PageRenderTime 35ms CodeModel.GetById 17ms app.highlight 12ms RepoModel.GetById 0ms app.codeStats 1ms

/library/Zend/Cache/Backend/File.php

https://bitbucket.org/hamidrezas/melobit
PHP | 1007 lines | 657 code | 36 blank | 314 comment | 59 complexity | 51f153ed71f20cc3131532c47bbb563f MD5 | raw file
Possible License(s): AGPL-1.0
   1<?php
   2/**
   3 * Zend Framework
   4 *
   5 * LICENSE
   6 *
   7 * This source file is subject to the new BSD license that is bundled
   8 * with this package in the file LICENSE.txt.
   9 * It is also available through the world-wide-web at this URL:
  10 * http://framework.zend.com/license/new-bsd
  11 * If you did not receive a copy of the license and are unable to
  12 * obtain it through the world-wide-web, please send an email
  13 * to license@zend.com so we can send you a copy immediately.
  14 *
  15 * @category   Zend
  16 * @package    Zend_Cache
  17 * @subpackage Zend_Cache_Backend
  18 * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  19 * @license    http://framework.zend.com/license/new-bsd     New BSD License
  20 * @version    $Id: File.php 24594 2012-01-05 21:27:01Z matthew $
  21 */
  22
  23/**
  24 * @see Zend_Cache_Backend_Interface
  25 */
  26require_once 'Zend/Cache/Backend/ExtendedInterface.php';
  27
  28/**
  29 * @see Zend_Cache_Backend
  30 */
  31require_once 'Zend/Cache/Backend.php';
  32
  33
  34/**
  35 * @package    Zend_Cache
  36 * @subpackage Zend_Cache_Backend
  37 * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  38 * @license    http://framework.zend.com/license/new-bsd     New BSD License
  39 */
  40class Zend_Cache_Backend_File extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
  41{
  42    /**
  43     * Available options
  44     *
  45     * =====> (string) cache_dir :
  46     * - Directory where to put the cache files
  47     *
  48     * =====> (boolean) file_locking :
  49     * - Enable / disable file_locking
  50     * - Can avoid cache corruption under bad circumstances but it doesn't work on multithread
  51     * webservers and on NFS filesystems for example
  52     *
  53     * =====> (boolean) read_control :
  54     * - Enable / disable read control
  55     * - If enabled, a control key is embeded in cache file and this key is compared with the one
  56     * calculated after the reading.
  57     *
  58     * =====> (string) read_control_type :
  59     * - Type of read control (only if read control is enabled). Available values are :
  60     *   'md5' for a md5 hash control (best but slowest)
  61     *   'crc32' for a crc32 hash control (lightly less safe but faster, better choice)
  62     *   'adler32' for an adler32 hash control (excellent choice too, faster than crc32)
  63     *   'strlen' for a length only test (fastest)
  64     *
  65     * =====> (int) hashed_directory_level :
  66     * - Hashed directory level
  67     * - Set the hashed directory structure level. 0 means "no hashed directory
  68     * structure", 1 means "one level of directory", 2 means "two levels"...
  69     * This option can speed up the cache only when you have many thousands of
  70     * cache file. Only specific benchs can help you to choose the perfect value
  71     * for you. Maybe, 1 or 2 is a good start.
  72     *
  73     * =====> (int) hashed_directory_umask :
  74     * - Umask for hashed directory structure
  75     *
  76     * =====> (string) file_name_prefix :
  77     * - prefix for cache files
  78     * - be really carefull with this option because a too generic value in a system cache dir
  79     *   (like /tmp) can cause disasters when cleaning the cache
  80     *
  81     * =====> (int) cache_file_umask :
  82     * - Umask for cache files
  83     *
  84     * =====> (int) metatadatas_array_max_size :
  85     * - max size for the metadatas array (don't change this value unless you
  86     *   know what you are doing)
  87     *
  88     * @var array available options
  89     */
  90    protected $_options = array(
  91        'cache_dir' => null,
  92        'file_locking' => true,
  93        'read_control' => true,
  94        'read_control_type' => 'crc32',
  95        'hashed_directory_level' => 0,
  96        'hashed_directory_umask' => 0700,
  97        'file_name_prefix' => 'zend_cache',
  98        'cache_file_umask' => 0600,
  99        'metadatas_array_max_size' => 100
 100    );
 101
 102    /**
 103     * Array of metadatas (each item is an associative array)
 104     *
 105     * @var array
 106     */
 107    protected $_metadatasArray = array();
 108
 109
 110    /**
 111     * Constructor
 112     *
 113     * @param  array $options associative array of options
 114     * @throws Zend_Cache_Exception
 115     * @return void
 116     */
 117    public function __construct(array $options = array())
 118    {
 119        parent::__construct($options);
 120        if ($this->_options['cache_dir'] !== null) { // particular case for this option
 121            $this->setCacheDir($this->_options['cache_dir']);
 122        } else {
 123            $this->setCacheDir(self::getTmpDir() . DIRECTORY_SEPARATOR, false);
 124        }
 125        if (isset($this->_options['file_name_prefix'])) { // particular case for this option
 126            if (!preg_match('~^[a-zA-Z0-9_]+$~D', $this->_options['file_name_prefix'])) {
 127                Zend_Cache::throwException('Invalid file_name_prefix : must use only [a-zA-Z0-9_]');
 128            }
 129        }
 130        if ($this->_options['metadatas_array_max_size'] < 10) {
 131            Zend_Cache::throwException('Invalid metadatas_array_max_size, must be > 10');
 132        }
 133        if (isset($options['hashed_directory_umask']) && is_string($options['hashed_directory_umask'])) {
 134            // See #ZF-4422
 135            $this->_options['hashed_directory_umask'] = octdec($this->_options['hashed_directory_umask']);
 136        }
 137        if (isset($options['cache_file_umask']) && is_string($options['cache_file_umask'])) {
 138            // See #ZF-4422
 139            $this->_options['cache_file_umask'] = octdec($this->_options['cache_file_umask']);
 140        }
 141    }
 142
 143    /**
 144     * Set the cache_dir (particular case of setOption() method)
 145     *
 146     * @param  string  $value
 147     * @param  boolean $trailingSeparator If true, add a trailing separator is necessary
 148     * @throws Zend_Cache_Exception
 149     * @return void
 150     */
 151    public function setCacheDir($value, $trailingSeparator = true)
 152    {
 153        if (!is_dir($value)) {
 154            Zend_Cache::throwException('cache_dir must be a directory');
 155        }
 156        if (!is_writable($value)) {
 157            Zend_Cache::throwException('cache_dir is not writable');
 158        }
 159        if ($trailingSeparator) {
 160            // add a trailing DIRECTORY_SEPARATOR if necessary
 161            $value = rtrim(realpath($value), '\\/') . DIRECTORY_SEPARATOR;
 162        }
 163        $this->_options['cache_dir'] = $value;
 164    }
 165
 166    /**
 167     * Test if a cache is available for the given id and (if yes) return it (false else)
 168     *
 169     * @param string $id cache id
 170     * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
 171     * @return string|false cached datas
 172     */
 173    public function load($id, $doNotTestCacheValidity = false)
 174    {
 175        if (!($this->_test($id, $doNotTestCacheValidity))) {
 176            // The cache is not hit !
 177            return false;
 178        }
 179        $metadatas = $this->_getMetadatas($id);
 180        $file = $this->_file($id);
 181        $data = $this->_fileGetContents($file);
 182        if ($this->_options['read_control']) {
 183            $hashData = $this->_hash($data, $this->_options['read_control_type']);
 184            $hashControl = $metadatas['hash'];
 185            if ($hashData != $hashControl) {
 186                // Problem detected by the read control !
 187                $this->_log('Zend_Cache_Backend_File::load() / read_control : stored hash and computed hash do not match');
 188                $this->remove($id);
 189                return false;
 190            }
 191        }
 192        return $data;
 193    }
 194
 195    /**
 196     * Test if a cache is available or not (for the given id)
 197     *
 198     * @param string $id cache id
 199     * @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
 200     */
 201    public function test($id)
 202    {
 203        clearstatcache();
 204        return $this->_test($id, false);
 205    }
 206
 207    /**
 208     * Save some string datas into a cache record
 209     *
 210     * Note : $data is always "string" (serialization is done by the
 211     * core not by the backend)
 212     *
 213     * @param  string $data             Datas to cache
 214     * @param  string $id               Cache id
 215     * @param  array  $tags             Array of strings, the cache record will be tagged by each string entry
 216     * @param  int    $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
 217     * @return boolean true if no problem
 218     */
 219    public function save($data, $id, $tags = array(), $specificLifetime = false)
 220    {
 221        clearstatcache();
 222        $file = $this->_file($id);
 223        $path = $this->_path($id);
 224        if ($this->_options['hashed_directory_level'] > 0) {
 225            if (!is_writable($path)) {
 226                // maybe, we just have to build the directory structure
 227                $this->_recursiveMkdirAndChmod($id);
 228            }
 229            if (!is_writable($path)) {
 230                return false;
 231            }
 232        }
 233        if ($this->_options['read_control']) {
 234            $hash = $this->_hash($data, $this->_options['read_control_type']);
 235        } else {
 236            $hash = '';
 237        }
 238        $metadatas = array(
 239            'hash' => $hash,
 240            'mtime' => time(),
 241            'expire' => $this->_expireTime($this->getLifetime($specificLifetime)),
 242            'tags' => $tags
 243        );
 244        $res = $this->_setMetadatas($id, $metadatas);
 245        if (!$res) {
 246            $this->_log('Zend_Cache_Backend_File::save() / error on saving metadata');
 247            return false;
 248        }
 249        $res = $this->_filePutContents($file, $data);
 250        return $res;
 251    }
 252
 253    /**
 254     * Remove a cache record
 255     *
 256     * @param  string $id cache id
 257     * @return boolean true if no problem
 258     */
 259    public function remove($id)
 260    {
 261        $file = $this->_file($id);
 262        $boolRemove   = $this->_remove($file);
 263        $boolMetadata = $this->_delMetadatas($id);
 264        return $boolMetadata && $boolRemove;
 265    }
 266
 267    /**
 268     * Clean some cache records
 269     *
 270     * Available modes are :
 271     *
 272     * Zend_Cache::CLEANING_MODE_ALL (default)    => remove all cache entries ($tags is not used)
 273     * Zend_Cache::CLEANING_MODE_OLD              => remove too old cache entries ($tags is not used)
 274     * Zend_Cache::CLEANING_MODE_MATCHING_TAG     => remove cache entries matching all given tags
 275     *                                               ($tags can be an array of strings or a single string)
 276     * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
 277     *                                               ($tags can be an array of strings or a single string)
 278     * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
 279     *                                               ($tags can be an array of strings or a single string)
 280     *
 281     * @param string $mode clean mode
 282     * @param tags array $tags array of tags
 283     * @return boolean true if no problem
 284     */
 285    public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
 286    {
 287        // We use this protected method to hide the recursive stuff
 288        clearstatcache();
 289        return $this->_clean($this->_options['cache_dir'], $mode, $tags);
 290    }
 291
 292    /**
 293     * Return an array of stored cache ids
 294     *
 295     * @return array array of stored cache ids (string)
 296     */
 297    public function getIds()
 298    {
 299        return $this->_get($this->_options['cache_dir'], 'ids', array());
 300    }
 301
 302    /**
 303     * Return an array of stored tags
 304     *
 305     * @return array array of stored tags (string)
 306     */
 307    public function getTags()
 308    {
 309        return $this->_get($this->_options['cache_dir'], 'tags', array());
 310    }
 311
 312    /**
 313     * Return an array of stored cache ids which match given tags
 314     *
 315     * In case of multiple tags, a logical AND is made between tags
 316     *
 317     * @param array $tags array of tags
 318     * @return array array of matching cache ids (string)
 319     */
 320    public function getIdsMatchingTags($tags = array())
 321    {
 322        return $this->_get($this->_options['cache_dir'], 'matching', $tags);
 323    }
 324
 325    /**
 326     * Return an array of stored cache ids which don't match given tags
 327     *
 328     * In case of multiple tags, a logical OR is made between tags
 329     *
 330     * @param array $tags array of tags
 331     * @return array array of not matching cache ids (string)
 332     */
 333    public function getIdsNotMatchingTags($tags = array())
 334    {
 335        return $this->_get($this->_options['cache_dir'], 'notMatching', $tags);
 336    }
 337
 338    /**
 339     * Return an array of stored cache ids which match any given tags
 340     *
 341     * In case of multiple tags, a logical AND is made between tags
 342     *
 343     * @param array $tags array of tags
 344     * @return array array of any matching cache ids (string)
 345     */
 346    public function getIdsMatchingAnyTags($tags = array())
 347    {
 348        return $this->_get($this->_options['cache_dir'], 'matchingAny', $tags);
 349    }
 350
 351    /**
 352     * Return the filling percentage of the backend storage
 353     *
 354     * @throws Zend_Cache_Exception
 355     * @return int integer between 0 and 100
 356     */
 357    public function getFillingPercentage()
 358    {
 359        $free = disk_free_space($this->_options['cache_dir']);
 360        $total = disk_total_space($this->_options['cache_dir']);
 361        if ($total == 0) {
 362            Zend_Cache::throwException('can\'t get disk_total_space');
 363        } else {
 364            if ($free >= $total) {
 365                return 100;
 366            }
 367            return ((int) (100. * ($total - $free) / $total));
 368        }
 369    }
 370
 371    /**
 372     * Return an array of metadatas for the given cache id
 373     *
 374     * The array must include these keys :
 375     * - expire : the expire timestamp
 376     * - tags : a string array of tags
 377     * - mtime : timestamp of last modification time
 378     *
 379     * @param string $id cache id
 380     * @return array array of metadatas (false if the cache id is not found)
 381     */
 382    public function getMetadatas($id)
 383    {
 384        $metadatas = $this->_getMetadatas($id);
 385        if (!$metadatas) {
 386            return false;
 387        }
 388        if (time() > $metadatas['expire']) {
 389            return false;
 390        }
 391        return array(
 392            'expire' => $metadatas['expire'],
 393            'tags' => $metadatas['tags'],
 394            'mtime' => $metadatas['mtime']
 395        );
 396    }
 397
 398    /**
 399     * Give (if possible) an extra lifetime to the given cache id
 400     *
 401     * @param string $id cache id
 402     * @param int $extraLifetime
 403     * @return boolean true if ok
 404     */
 405    public function touch($id, $extraLifetime)
 406    {
 407        $metadatas = $this->_getMetadatas($id);
 408        if (!$metadatas) {
 409            return false;
 410        }
 411        if (time() > $metadatas['expire']) {
 412            return false;
 413        }
 414        $newMetadatas = array(
 415            'hash' => $metadatas['hash'],
 416            'mtime' => time(),
 417            'expire' => $metadatas['expire'] + $extraLifetime,
 418            'tags' => $metadatas['tags']
 419        );
 420        $res = $this->_setMetadatas($id, $newMetadatas);
 421        if (!$res) {
 422            return false;
 423        }
 424        return true;
 425    }
 426
 427    /**
 428     * Return an associative array of capabilities (booleans) of the backend
 429     *
 430     * The array must include these keys :
 431     * - automatic_cleaning (is automating cleaning necessary)
 432     * - tags (are tags supported)
 433     * - expired_read (is it possible to read expired cache records
 434     *                 (for doNotTestCacheValidity option for example))
 435     * - priority does the backend deal with priority when saving
 436     * - infinite_lifetime (is infinite lifetime can work with this backend)
 437     * - get_list (is it possible to get the list of cache ids and the complete list of tags)
 438     *
 439     * @return array associative of with capabilities
 440     */
 441    public function getCapabilities()
 442    {
 443        return array(
 444            'automatic_cleaning' => true,
 445            'tags' => true,
 446            'expired_read' => true,
 447            'priority' => false,
 448            'infinite_lifetime' => true,
 449            'get_list' => true
 450        );
 451    }
 452
 453    /**
 454     * PUBLIC METHOD FOR UNIT TESTING ONLY !
 455     *
 456     * Force a cache record to expire
 457     *
 458     * @param string $id cache id
 459     */
 460    public function ___expire($id)
 461    {
 462        $metadatas = $this->_getMetadatas($id);
 463        if ($metadatas) {
 464            $metadatas['expire'] = 1;
 465            $this->_setMetadatas($id, $metadatas);
 466        }
 467    }
 468
 469    /**
 470     * Get a metadatas record
 471     *
 472     * @param  string $id  Cache id
 473     * @return array|false Associative array of metadatas
 474     */
 475    protected function _getMetadatas($id)
 476    {
 477        if (isset($this->_metadatasArray[$id])) {
 478            return $this->_metadatasArray[$id];
 479        } else {
 480            $metadatas = $this->_loadMetadatas($id);
 481            if (!$metadatas) {
 482                return false;
 483            }
 484            $this->_setMetadatas($id, $metadatas, false);
 485            return $metadatas;
 486        }
 487    }
 488
 489    /**
 490     * Set a metadatas record
 491     *
 492     * @param  string $id        Cache id
 493     * @param  array  $metadatas Associative array of metadatas
 494     * @param  boolean $save     optional pass false to disable saving to file
 495     * @return boolean True if no problem
 496     */
 497    protected function _setMetadatas($id, $metadatas, $save = true)
 498    {
 499        if (count($this->_metadatasArray) >= $this->_options['metadatas_array_max_size']) {
 500            $n = (int) ($this->_options['metadatas_array_max_size'] / 10);
 501            $this->_metadatasArray = array_slice($this->_metadatasArray, $n);
 502        }
 503        if ($save) {
 504            $result = $this->_saveMetadatas($id, $metadatas);
 505            if (!$result) {
 506                return false;
 507            }
 508        }
 509        $this->_metadatasArray[$id] = $metadatas;
 510        return true;
 511    }
 512
 513    /**
 514     * Drop a metadata record
 515     *
 516     * @param  string $id Cache id
 517     * @return boolean True if no problem
 518     */
 519    protected function _delMetadatas($id)
 520    {
 521        if (isset($this->_metadatasArray[$id])) {
 522            unset($this->_metadatasArray[$id]);
 523        }
 524        $file = $this->_metadatasFile($id);
 525        return $this->_remove($file);
 526    }
 527
 528    /**
 529     * Clear the metadatas array
 530     *
 531     * @return void
 532     */
 533    protected function _cleanMetadatas()
 534    {
 535        $this->_metadatasArray = array();
 536    }
 537
 538    /**
 539     * Load metadatas from disk
 540     *
 541     * @param  string $id Cache id
 542     * @return array|false Metadatas associative array
 543     */
 544    protected function _loadMetadatas($id)
 545    {
 546        $file = $this->_metadatasFile($id);
 547        $result = $this->_fileGetContents($file);
 548        if (!$result) {
 549            return false;
 550        }
 551        $tmp = @unserialize($result);
 552        return $tmp;
 553    }
 554
 555    /**
 556     * Save metadatas to disk
 557     *
 558     * @param  string $id        Cache id
 559     * @param  array  $metadatas Associative array
 560     * @return boolean True if no problem
 561     */
 562    protected function _saveMetadatas($id, $metadatas)
 563    {
 564        $file = $this->_metadatasFile($id);
 565        $result = $this->_filePutContents($file, serialize($metadatas));
 566        if (!$result) {
 567            return false;
 568        }
 569        return true;
 570    }
 571
 572    /**
 573     * Make and return a file name (with path) for metadatas
 574     *
 575     * @param  string $id Cache id
 576     * @return string Metadatas file name (with path)
 577     */
 578    protected function _metadatasFile($id)
 579    {
 580        $path = $this->_path($id);
 581        $fileName = $this->_idToFileName('internal-metadatas---' . $id);
 582        return $path . $fileName;
 583    }
 584
 585    /**
 586     * Check if the given filename is a metadatas one
 587     *
 588     * @param  string $fileName File name
 589     * @return boolean True if it's a metadatas one
 590     */
 591    protected function _isMetadatasFile($fileName)
 592    {
 593        $id = $this->_fileNameToId($fileName);
 594        if (substr($id, 0, 21) == 'internal-metadatas---') {
 595            return true;
 596        } else {
 597            return false;
 598        }
 599    }
 600
 601    /**
 602     * Remove a file
 603     *
 604     * If we can't remove the file (because of locks or any problem), we will touch
 605     * the file to invalidate it
 606     *
 607     * @param  string $file Complete file path
 608     * @return boolean True if ok
 609     */
 610    protected function _remove($file)
 611    {
 612        if (!is_file($file)) {
 613            return false;
 614        }
 615        if (!@unlink($file)) {
 616            # we can't remove the file (because of locks or any problem)
 617            $this->_log("Zend_Cache_Backend_File::_remove() : we can't remove $file");
 618            return false;
 619        }
 620        return true;
 621    }
 622
 623    /**
 624     * Clean some cache records (protected method used for recursive stuff)
 625     *
 626     * Available modes are :
 627     * Zend_Cache::CLEANING_MODE_ALL (default)    => remove all cache entries ($tags is not used)
 628     * Zend_Cache::CLEANING_MODE_OLD              => remove too old cache entries ($tags is not used)
 629     * Zend_Cache::CLEANING_MODE_MATCHING_TAG     => remove cache entries matching all given tags
 630     *                                               ($tags can be an array of strings or a single string)
 631     * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
 632     *                                               ($tags can be an array of strings or a single string)
 633     * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
 634     *                                               ($tags can be an array of strings or a single string)
 635     *
 636     * @param  string $dir  Directory to clean
 637     * @param  string $mode Clean mode
 638     * @param  array  $tags Array of tags
 639     * @throws Zend_Cache_Exception
 640     * @return boolean True if no problem
 641     */
 642    protected function _clean($dir, $mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
 643    {
 644        if (!is_dir($dir)) {
 645            return false;
 646        }
 647        $result = true;
 648        $prefix = $this->_options['file_name_prefix'];
 649        $glob = @glob($dir . $prefix . '--*');
 650        if ($glob === false) {
 651            // On some systems it is impossible to distinguish between empty match and an error.
 652            return true;
 653        }
 654        foreach ($glob as $file)  {
 655            if (is_file($file)) {
 656                $fileName = basename($file);
 657                if ($this->_isMetadatasFile($fileName)) {
 658                    // in CLEANING_MODE_ALL, we drop anything, even remainings old metadatas files
 659                    if ($mode != Zend_Cache::CLEANING_MODE_ALL) {
 660                        continue;
 661                    }
 662                }
 663                $id = $this->_fileNameToId($fileName);
 664                $metadatas = $this->_getMetadatas($id);
 665                if ($metadatas === FALSE) {
 666                    $metadatas = array('expire' => 1, 'tags' => array());
 667                }
 668                switch ($mode) {
 669                    case Zend_Cache::CLEANING_MODE_ALL:
 670                        $res = $this->remove($id);
 671                        if (!$res) {
 672                            // in this case only, we accept a problem with the metadatas file drop
 673                            $res = $this->_remove($file);
 674                        }
 675                        $result = $result && $res;
 676                        break;
 677                    case Zend_Cache::CLEANING_MODE_OLD:
 678                        if (time() > $metadatas['expire']) {
 679                            $result = $this->remove($id) && $result;
 680                        }
 681                        break;
 682                    case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
 683                        $matching = true;
 684                        foreach ($tags as $tag) {
 685                            if (!in_array($tag, $metadatas['tags'])) {
 686                                $matching = false;
 687                                break;
 688                            }
 689                        }
 690                        if ($matching) {
 691                            $result = $this->remove($id) && $result;
 692                        }
 693                        break;
 694                    case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
 695                        $matching = false;
 696                        foreach ($tags as $tag) {
 697                            if (in_array($tag, $metadatas['tags'])) {
 698                                $matching = true;
 699                                break;
 700                            }
 701                        }
 702                        if (!$matching) {
 703                            $result = $this->remove($id) && $result;
 704                        }
 705                        break;
 706                    case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
 707                        $matching = false;
 708                        foreach ($tags as $tag) {
 709                            if (in_array($tag, $metadatas['tags'])) {
 710                                $matching = true;
 711                                break;
 712                            }
 713                        }
 714                        if ($matching) {
 715                            $result = $this->remove($id) && $result;
 716                        }
 717                        break;
 718                    default:
 719                        Zend_Cache::throwException('Invalid mode for clean() method');
 720                        break;
 721                }
 722            }
 723            if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) {
 724                // Recursive call
 725                $result = $this->_clean($file . DIRECTORY_SEPARATOR, $mode, $tags) && $result;
 726                if ($mode == Zend_Cache::CLEANING_MODE_ALL) {
 727                    // we try to drop the structure too
 728                    @rmdir($file);
 729                }
 730            }
 731        }
 732        return $result;
 733    }
 734
 735    protected function _get($dir, $mode, $tags = array())
 736    {
 737        if (!is_dir($dir)) {
 738            return false;
 739        }
 740        $result = array();
 741        $prefix = $this->_options['file_name_prefix'];
 742        $glob = @glob($dir . $prefix . '--*');
 743        if ($glob === false) {
 744            // On some systems it is impossible to distinguish between empty match and an error.
 745            return array();
 746        }
 747        foreach ($glob as $file)  {
 748            if (is_file($file)) {
 749                $fileName = basename($file);
 750                $id = $this->_fileNameToId($fileName);
 751                $metadatas = $this->_getMetadatas($id);
 752                if ($metadatas === FALSE) {
 753                    continue;
 754                }
 755                if (time() > $metadatas['expire']) {
 756                    continue;
 757                }
 758                switch ($mode) {
 759                    case 'ids':
 760                        $result[] = $id;
 761                        break;
 762                    case 'tags':
 763                        $result = array_unique(array_merge($result, $metadatas['tags']));
 764                        break;
 765                    case 'matching':
 766                        $matching = true;
 767                        foreach ($tags as $tag) {
 768                            if (!in_array($tag, $metadatas['tags'])) {
 769                                $matching = false;
 770                                break;
 771                            }
 772                        }
 773                        if ($matching) {
 774                            $result[] = $id;
 775                        }
 776                        break;
 777                    case 'notMatching':
 778                        $matching = false;
 779                        foreach ($tags as $tag) {
 780                            if (in_array($tag, $metadatas['tags'])) {
 781                                $matching = true;
 782                                break;
 783                            }
 784                        }
 785                        if (!$matching) {
 786                            $result[] = $id;
 787                        }
 788                        break;
 789                    case 'matchingAny':
 790                        $matching = false;
 791                        foreach ($tags as $tag) {
 792                            if (in_array($tag, $metadatas['tags'])) {
 793                                $matching = true;
 794                                break;
 795                            }
 796                        }
 797                        if ($matching) {
 798                            $result[] = $id;
 799                        }
 800                        break;
 801                    default:
 802                        Zend_Cache::throwException('Invalid mode for _get() method');
 803                        break;
 804                }
 805            }
 806            if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) {
 807                // Recursive call
 808                $recursiveRs =  $this->_get($file . DIRECTORY_SEPARATOR, $mode, $tags);
 809                if ($recursiveRs === false) {
 810                    $this->_log('Zend_Cache_Backend_File::_get() / recursive call : can\'t list entries of "'.$file.'"');
 811                } else {
 812                    $result = array_unique(array_merge($result, $recursiveRs));
 813                }
 814            }
 815        }
 816        return array_unique($result);
 817    }
 818
 819    /**
 820     * Compute & return the expire time
 821     *
 822     * @return int expire time (unix timestamp)
 823     */
 824    protected function _expireTime($lifetime)
 825    {
 826        if ($lifetime === null) {
 827            return 9999999999;
 828        }
 829        return time() + $lifetime;
 830    }
 831
 832    /**
 833     * Make a control key with the string containing datas
 834     *
 835     * @param  string $data        Data
 836     * @param  string $controlType Type of control 'md5', 'crc32' or 'strlen'
 837     * @throws Zend_Cache_Exception
 838     * @return string Control key
 839     */
 840    protected function _hash($data, $controlType)
 841    {
 842        switch ($controlType) {
 843        case 'md5':
 844            return md5($data);
 845        case 'crc32':
 846            return crc32($data);
 847        case 'strlen':
 848            return strlen($data);
 849        case 'adler32':
 850            return hash('adler32', $data);
 851        default:
 852            Zend_Cache::throwException("Incorrect hash function : $controlType");
 853        }
 854    }
 855
 856    /**
 857     * Transform a cache id into a file name and return it
 858     *
 859     * @param  string $id Cache id
 860     * @return string File name
 861     */
 862    protected function _idToFileName($id)
 863    {
 864        $prefix = $this->_options['file_name_prefix'];
 865        $result = $prefix . '---' . $id;
 866        return $result;
 867    }
 868
 869    /**
 870     * Make and return a file name (with path)
 871     *
 872     * @param  string $id Cache id
 873     * @return string File name (with path)
 874     */
 875    protected function _file($id)
 876    {
 877        $path = $this->_path($id);
 878        $fileName = $this->_idToFileName($id);
 879        return $path . $fileName;
 880    }
 881
 882    /**
 883     * Return the complete directory path of a filename (including hashedDirectoryStructure)
 884     *
 885     * @param  string $id Cache id
 886     * @param  boolean $parts if true, returns array of directory parts instead of single string
 887     * @return string Complete directory path
 888     */
 889    protected function _path($id, $parts = false)
 890    {
 891        $partsArray = array();
 892        $root = $this->_options['cache_dir'];
 893        $prefix = $this->_options['file_name_prefix'];
 894        if ($this->_options['hashed_directory_level']>0) {
 895            $hash = hash('adler32', $id);
 896            for ($i=0 ; $i < $this->_options['hashed_directory_level'] ; $i++) {
 897                $root = $root . $prefix . '--' . substr($hash, 0, $i + 1) . DIRECTORY_SEPARATOR;
 898                $partsArray[] = $root;
 899            }
 900        }
 901        if ($parts) {
 902            return $partsArray;
 903        } else {
 904            return $root;
 905        }
 906    }
 907
 908    /**
 909     * Make the directory strucuture for the given id
 910     *
 911     * @param string $id cache id
 912     * @return boolean true
 913     */
 914    protected function _recursiveMkdirAndChmod($id)
 915    {
 916        if ($this->_options['hashed_directory_level'] <=0) {
 917            return true;
 918        }
 919        $partsArray = $this->_path($id, true);
 920        foreach ($partsArray as $part) {
 921            if (!is_dir($part)) {
 922                @mkdir($part, $this->_options['hashed_directory_umask']);
 923                @chmod($part, $this->_options['hashed_directory_umask']); // see #ZF-320 (this line is required in some configurations)
 924            }
 925        }
 926        return true;
 927    }
 928
 929    /**
 930     * Test if the given cache id is available (and still valid as a cache record)
 931     *
 932     * @param  string  $id                     Cache id
 933     * @param  boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
 934     * @return boolean|mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
 935     */
 936    protected function _test($id, $doNotTestCacheValidity)
 937    {
 938        $metadatas = $this->_getMetadatas($id);
 939        if (!$metadatas) {
 940            return false;
 941        }
 942        if ($doNotTestCacheValidity || (time() <= $metadatas['expire'])) {
 943            return $metadatas['mtime'];
 944        }
 945        return false;
 946    }
 947
 948    /**
 949     * Return the file content of the given file
 950     *
 951     * @param  string $file File complete path
 952     * @return string File content (or false if problem)
 953     */
 954    protected function _fileGetContents($file)
 955    {
 956        $result = false;
 957        if (!is_file($file)) {
 958            return false;
 959        }
 960        $f = @fopen($file, 'rb');
 961        if ($f) {
 962            if ($this->_options['file_locking']) @flock($f, LOCK_SH);
 963            $result = stream_get_contents($f);
 964            if ($this->_options['file_locking']) @flock($f, LOCK_UN);
 965            @fclose($f);
 966        }
 967        return $result;
 968    }
 969
 970    /**
 971     * Put the given string into the given file
 972     *
 973     * @param  string $file   File complete path
 974     * @param  string $string String to put in file
 975     * @return boolean true if no problem
 976     */
 977    protected function _filePutContents($file, $string)
 978    {
 979        $result = false;
 980        $f = @fopen($file, 'ab+');
 981        if ($f) {
 982            if ($this->_options['file_locking']) @flock($f, LOCK_EX);
 983            fseek($f, 0);
 984            ftruncate($f, 0);
 985            $tmp = @fwrite($f, $string);
 986            if (!($tmp === FALSE)) {
 987                $result = true;
 988            }
 989            @fclose($f);
 990        }
 991        @chmod($file, $this->_options['cache_file_umask']);
 992        return $result;
 993    }
 994
 995    /**
 996     * Transform a file name into cache id and return it
 997     *
 998     * @param  string $fileName File name
 999     * @return string Cache id
1000     */
1001    protected function _fileNameToId($fileName)
1002    {
1003        $prefix = $this->_options['file_name_prefix'];
1004        return preg_replace('~^' . $prefix . '---(.*)$~', '$1', $fileName);
1005    }
1006
1007}