PageRenderTime 772ms CodeModel.GetById 131ms app.highlight 427ms RepoModel.GetById 98ms app.codeStats 2ms

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

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