3
* Copyright 1999-2014 Horde LLC (http://www.horde.org/)
5
* See the enclosed file COPYING for license information (LGPL). If you
6
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
9
* @copyright 1999-2014 Horde LLC
10
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
15
* Cache storage in the filesystem.
17
* @author Anil Madhavapeddy <anil@recoil.org>
18
* @author Chuck Hagenbuch <chuck@horde.org>
19
* @author Michael Slusarz <slusarz@horde.org>
21
* @copyright 1999-2014 Horde LLC
22
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
25
class Horde_Cache_Storage_File extends Horde_Cache_Storage_Base
27
/* Location of the garbage collection data file. */
28
const GC_FILE = 'horde_cache_gc';
31
* List of key to filename mappings.
35
protected $_file = array();
40
* @param array $params Optional parameters:
42
* - dir: (string) The base directory to store the cache files in.
43
* DEFAULT: System default
44
* - no_gc: (boolean) If true, don't perform garbage collection.
46
* - prefix: (string) The filename prefix to use for the cache files.
48
* - sub: (integer) If non-zero, the number of subdirectories to create
49
* to store the file (i.e. PHP's session.save_path).
53
public function __construct(array $params = array())
55
$params = array_merge(array(
60
if (!isset($params['dir']) || !@is_dir($params['dir'])) {
61
$params['dir'] = sys_get_temp_dir();
64
parent::__construct($params);
70
public function __destruct()
74
/* Only do garbage collection 0.1% of the time we create an object. */
75
if (!empty($this->_params['no_gc']) ||
76
(intval(substr($c_time, -3)) !== 0)) {
80
$filename = $this->_params['dir'] . '/' . self::GC_FILE;
83
if (is_readable($filename)) {
84
$gc_file = file($filename, FILE_IGNORE_NEW_LINES);
87
while (list(,$data) = each($gc_file)) {
88
$parts = explode("\t", $data, 2);
89
$excepts[$parts[0]] = $parts[1];
93
foreach ($this->_getCacheFiles() as $fname => $pname) {
94
if (!empty($excepts[$fname]) &&
95
(($c_time - $excepts[$fname]) > filemtime($pname))) {
97
unset($excepts[$fname]);
101
if ($fp = @fopen($filename, 'w')) {
102
foreach ($excepts as $key => $val) {
103
fwrite($fp, $key . "\t" . $val . "\n");
111
public function get($key, $lifetime = 0)
113
if (!$this->exists($key, $lifetime)) {
114
/* Nothing cached, return failure. */
118
$filename = $this->_keyToFile($key);
119
$size = filesize($filename);
122
? @file_get_contents($filename)
128
public function set($key, $data, $lifetime = 0)
130
$filename = $this->_keyToFile($key, true);
131
$tmp_file = Horde_Util::getTempFile('HordeCache', true, $this->_params['dir']);
132
if (isset($this->_params['umask'])) {
133
chmod($tmp_file, 0666 & ~$this->_params['umask']);
136
if (file_put_contents($tmp_file, $data) === false) {
137
throw new Horde_Cache_Exception('Cannot write to cache directory ' . $this->_params['dir']);
140
@rename($tmp_file, $filename);
143
($fp = @fopen($this->_params['dir'] . '/' . self::GC_FILE, 'a'))) {
144
// This may result in duplicate entries in GC_FILE, but we
145
// will take care of these whenever we do GC and this is quicker
146
// than having to check every time we access the file.
147
fwrite($fp, $filename . "\t" . (time() + $lifetime) . "\n");
154
public function exists($key, $lifetime = 0)
156
$filename = $this->_keyToFile($key);
158
/* Key exists in the cache */
159
if (file_exists($filename)) {
160
/* 0 means no expire.
161
* Also, If the file was been created after the supplied value,
162
* the data is valid (fresh). */
163
if (($lifetime == 0) ||
164
(time() - $lifetime <= filemtime($filename))) {
176
public function expire($key)
178
return @unlink($this->_keyToFile($key));
183
public function clear()
185
foreach ($this->_getCacheFiles() as $val) {
188
@unlink($this->_params['dir'] . '/' . self::GC_FILE);
192
* Return a list of cache files.
194
* @return array Pathnames to cache files.
196
protected function _getCacheFiles()
201
$it = empty($this->_params['sub'])
202
? new DirectoryIterator($this->_params['dir'])
203
: new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->_params['dir']), RecursiveIteratorIterator::CHILD_FIRST);
204
} catch (UnexpectedValueException $e) {
208
foreach ($it as $val) {
209
if (!$val->isDir() &&
210
($fname = $val->getFilename()) &&
211
(strpos($fname, $this->_params['prefix']) === 0)) {
212
$paths[$fname] = $val->getPathname();
220
* Map a cache key to a unique filename.
222
* @param string $key Cache key.
223
* @param string $create Create path if it doesn't exist?
225
* @return string Fully qualified filename.
227
protected function _keyToFile($key, $create = false)
229
if ($create || !isset($this->_file[$key])) {
230
$dir = $this->_params['dir'] . '/';
231
$md5 = hash('md5', $key);
234
if (!empty($this->_params['sub'])) {
235
$max = min($this->_params['sub'], strlen($md5));
236
for ($i = 0; $i < $max; $i++) {
238
if ($create && !is_dir($dir . $sub)) {
239
if (!mkdir($dir . $sub)) {
248
$this->_file[$key] = $dir . $sub . $this->_params['prefix'] . $md5;
251
return $this->_file[$key];