Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
13532 anikendra 1
<?php
2
/**
3
 * File Storage engine for cache. Filestorage is the slowest cache storage
4
 * to read and write. However, it is good for servers that don't have other storage
5
 * engine available, or have content which is not performance sensitive.
6
 *
7
 * You can configure a FileEngine cache, using Cache::config()
8
 *
9
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
10
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
11
 *
12
 * Licensed under The MIT License
13
 * For full copyright and license information, please see the LICENSE.txt
14
 * Redistributions of files must retain the above copyright notice.
15
 *
16
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
17
 * @link          http://cakephp.org CakePHP(tm) Project
18
 * @since         CakePHP(tm) v 1.2.0.4933
19
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
20
 */
21
 
22
/**
23
 * File Storage engine for cache. Filestorage is the slowest cache storage
24
 * to read and write. However, it is good for servers that don't have other storage
25
 * engine available, or have content which is not performance sensitive.
26
 *
27
 * You can configure a FileEngine cache, using Cache::config()
28
 *
29
 * @package       Cake.Cache.Engine
30
 */
31
class FileEngine extends CacheEngine {
32
 
33
/**
34
 * Instance of SplFileObject class
35
 *
36
 * @var File
37
 */
38
	protected $_File = null;
39
 
40
/**
41
 * Settings
42
 *
43
 * - path = absolute path to cache directory, default => CACHE
44
 * - prefix = string prefix for filename, default => cake_
45
 * - lock = enable file locking on write, default => true
46
 * - serialize = serialize the data, default => true
47
 *
48
 * @var array
49
 * @see CacheEngine::__defaults
50
 */
51
	public $settings = array();
52
 
53
/**
54
 * True unless FileEngine::__active(); fails
55
 *
56
 * @var boolean
57
 */
58
	protected $_init = true;
59
 
60
/**
61
 * Initialize the Cache Engine
62
 *
63
 * Called automatically by the cache frontend
64
 * To reinitialize the settings call Cache::engine('EngineName', [optional] settings = array());
65
 *
66
 * @param array $settings array of setting for the engine
67
 * @return boolean True if the engine has been successfully initialized, false if not
68
 */
69
	public function init($settings = array()) {
70
		$settings += array(
71
			'engine' => 'File',
72
			'path' => CACHE,
73
			'prefix' => 'cake_',
74
			'lock' => true,
75
			'serialize' => true,
76
			'isWindows' => false,
77
			'mask' => 0664
78
		);
79
		parent::init($settings);
80
 
81
		if (DS === '\\') {
82
			$this->settings['isWindows'] = true;
83
		}
84
		if (substr($this->settings['path'], -1) !== DS) {
85
			$this->settings['path'] .= DS;
86
		}
87
		if (!empty($this->_groupPrefix)) {
88
			$this->_groupPrefix = str_replace('_', DS, $this->_groupPrefix);
89
		}
90
		return $this->_active();
91
	}
92
 
93
/**
94
 * Garbage collection. Permanently remove all expired and deleted data
95
 *
96
 * @param integer $expires [optional] An expires timestamp, invalidating all data before.
97
 * @return boolean True if garbage collection was successful, false on failure
98
 */
99
	public function gc($expires = null) {
100
		return $this->clear(true);
101
	}
102
 
103
/**
104
 * Write data for key into cache
105
 *
106
 * @param string $key Identifier for the data
107
 * @param mixed $data Data to be cached
108
 * @param integer $duration How long to cache the data, in seconds
109
 * @return boolean True if the data was successfully cached, false on failure
110
 */
111
	public function write($key, $data, $duration) {
112
		if ($data === '' || !$this->_init) {
113
			return false;
114
		}
115
 
116
		if ($this->_setKey($key, true) === false) {
117
			return false;
118
		}
119
 
120
		$lineBreak = "\n";
121
 
122
		if ($this->settings['isWindows']) {
123
			$lineBreak = "\r\n";
124
		}
125
 
126
		if (!empty($this->settings['serialize'])) {
127
			if ($this->settings['isWindows']) {
128
				$data = str_replace('\\', '\\\\\\\\', serialize($data));
129
			} else {
130
				$data = serialize($data);
131
			}
132
		}
133
 
134
		$expires = time() + $duration;
135
		$contents = $expires . $lineBreak . $data . $lineBreak;
136
 
137
		if ($this->settings['lock']) {
138
			$this->_File->flock(LOCK_EX);
139
		}
140
 
141
		$this->_File->rewind();
142
		$success = $this->_File->ftruncate(0) && $this->_File->fwrite($contents) && $this->_File->fflush();
143
 
144
		if ($this->settings['lock']) {
145
			$this->_File->flock(LOCK_UN);
146
		}
147
 
148
		return $success;
149
	}
150
 
151
/**
152
 * Read a key from the cache
153
 *
154
 * @param string $key Identifier for the data
155
 * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it
156
 */
157
	public function read($key) {
158
		if (!$this->_init || $this->_setKey($key) === false) {
159
			return false;
160
		}
161
 
162
		if ($this->settings['lock']) {
163
			$this->_File->flock(LOCK_SH);
164
		}
165
 
166
		$this->_File->rewind();
167
		$time = time();
168
		$cachetime = intval($this->_File->current());
169
 
170
		if ($cachetime !== false && ($cachetime < $time || ($time + $this->settings['duration']) < $cachetime)) {
171
			if ($this->settings['lock']) {
172
				$this->_File->flock(LOCK_UN);
173
			}
174
			return false;
175
		}
176
 
177
		$data = '';
178
		$this->_File->next();
179
		while ($this->_File->valid()) {
180
			$data .= $this->_File->current();
181
			$this->_File->next();
182
		}
183
 
184
		if ($this->settings['lock']) {
185
			$this->_File->flock(LOCK_UN);
186
		}
187
 
188
		$data = trim($data);
189
 
190
		if ($data !== '' && !empty($this->settings['serialize'])) {
191
			if ($this->settings['isWindows']) {
192
				$data = str_replace('\\\\\\\\', '\\', $data);
193
			}
194
			$data = unserialize((string)$data);
195
		}
196
		return $data;
197
	}
198
 
199
/**
200
 * Delete a key from the cache
201
 *
202
 * @param string $key Identifier for the data
203
 * @return boolean True if the value was successfully deleted, false if it didn't exist or couldn't be removed
204
 */
205
	public function delete($key) {
206
		if ($this->_setKey($key) === false || !$this->_init) {
207
			return false;
208
		}
209
		$path = $this->_File->getRealPath();
210
		$this->_File = null;
211
 
212
		//@codingStandardsIgnoreStart
213
		return @unlink($path);
214
		//@codingStandardsIgnoreEnd
215
	}
216
 
217
/**
218
 * Delete all values from the cache
219
 *
220
 * @param boolean $check Optional - only delete expired cache items
221
 * @return boolean True if the cache was successfully cleared, false otherwise
222
 */
223
	public function clear($check) {
224
		if (!$this->_init) {
225
			return false;
226
		}
227
		$this->_File = null;
228
 
229
		$threshold = $now = false;
230
		if ($check) {
231
			$now = time();
232
			$threshold = $now - $this->settings['duration'];
233
		}
234
 
235
		$this->_clearDirectory($this->settings['path'], $now, $threshold);
236
 
237
		$directory = new RecursiveDirectoryIterator($this->settings['path']);
238
		$contents = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
239
		$cleared = array();
240
		foreach ($contents as $path) {
241
			if ($path->isFile()) {
242
				continue;
243
			}
244
 
245
			$path = $path->getRealPath() . DS;
246
			if (!in_array($path, $cleared)) {
247
				$this->_clearDirectory($path, $now, $threshold);
248
				$cleared[] = $path;
249
			}
250
		}
251
		return true;
252
	}
253
 
254
/**
255
 * Used to clear a directory of matching files.
256
 *
257
 * @param string $path The path to search.
258
 * @param integer $now The current timestamp
259
 * @param integer $threshold Any file not modified after this value will be deleted.
260
 * @return void
261
 */
262
	protected function _clearDirectory($path, $now, $threshold) {
263
		$prefixLength = strlen($this->settings['prefix']);
264
 
265
		if (!is_dir($path)) {
266
			return;
267
		}
268
 
269
		$dir = dir($path);
270
		while (($entry = $dir->read()) !== false) {
271
			if (substr($entry, 0, $prefixLength) !== $this->settings['prefix']) {
272
				continue;
273
			}
274
			$filePath = $path . $entry;
275
			if (!file_exists($filePath) || is_dir($filePath)) {
276
				continue;
277
			}
278
			$file = new SplFileObject($path . $entry, 'r');
279
 
280
			if ($threshold) {
281
				$mtime = $file->getMTime();
282
 
283
				if ($mtime > $threshold) {
284
					continue;
285
				}
286
				$expires = (int)$file->current();
287
 
288
				if ($expires > $now) {
289
					continue;
290
				}
291
			}
292
			if ($file->isFile()) {
293
				$filePath = $file->getRealPath();
294
				$file = null;
295
 
296
				//@codingStandardsIgnoreStart
297
				@unlink($filePath);
298
				//@codingStandardsIgnoreEnd
299
			}
300
		}
301
	}
302
 
303
/**
304
 * Not implemented
305
 *
306
 * @param string $key
307
 * @param integer $offset
308
 * @return void
309
 * @throws CacheException
310
 */
311
	public function decrement($key, $offset = 1) {
312
		throw new CacheException(__d('cake_dev', 'Files cannot be atomically decremented.'));
313
	}
314
 
315
/**
316
 * Not implemented
317
 *
318
 * @param string $key
319
 * @param integer $offset
320
 * @return void
321
 * @throws CacheException
322
 */
323
	public function increment($key, $offset = 1) {
324
		throw new CacheException(__d('cake_dev', 'Files cannot be atomically incremented.'));
325
	}
326
 
327
/**
328
 * Sets the current cache key this class is managing, and creates a writable SplFileObject
329
 * for the cache file the key is referring to.
330
 *
331
 * @param string $key The key
332
 * @param boolean $createKey Whether the key should be created if it doesn't exists, or not
333
 * @return boolean true if the cache key could be set, false otherwise
334
 */
335
	protected function _setKey($key, $createKey = false) {
336
		$groups = null;
337
		if (!empty($this->_groupPrefix)) {
338
			$groups = vsprintf($this->_groupPrefix, $this->groups());
339
		}
340
		$dir = $this->settings['path'] . $groups;
341
 
342
		if (!is_dir($dir)) {
343
			mkdir($dir, 0775, true);
344
		}
345
		$path = new SplFileInfo($dir . $key);
346
 
347
		if (!$createKey && !$path->isFile()) {
348
			return false;
349
		}
350
		if (empty($this->_File) || $this->_File->getBaseName() !== $key) {
351
			$exists = file_exists($path->getPathname());
352
			try {
353
				$this->_File = $path->openFile('c+');
354
			} catch (Exception $e) {
355
				trigger_error($e->getMessage(), E_USER_WARNING);
356
				return false;
357
			}
358
			unset($path);
359
 
360
			if (!$exists && !chmod($this->_File->getPathname(), (int)$this->settings['mask'])) {
361
				trigger_error(__d(
362
					'cake_dev', 'Could not apply permission mask "%s" on cache file "%s"',
363
					array($this->_File->getPathname(), $this->settings['mask'])), E_USER_WARNING);
364
			}
365
		}
366
		return true;
367
	}
368
 
369
/**
370
 * Determine is cache directory is writable
371
 *
372
 * @return boolean
373
 */
374
	protected function _active() {
375
		$dir = new SplFileInfo($this->settings['path']);
376
		if (Configure::read('debug')) {
377
			$path = $dir->getPathname();
378
			if (!is_dir($path)) {
379
				mkdir($path, 0775, true);
380
			}
381
		}
382
		if ($this->_init && !($dir->isDir() && $dir->isWritable())) {
383
			$this->_init = false;
384
			trigger_error(__d('cake_dev', '%s is not writable', $this->settings['path']), E_USER_WARNING);
385
			return false;
386
		}
387
		return true;
388
	}
389
 
390
/**
391
 * Generates a safe key for use with cache engine storage engines.
392
 *
393
 * @param string $key the key passed over
394
 * @return mixed string $key or false
395
 */
396
	public function key($key) {
397
		if (empty($key)) {
398
			return false;
399
		}
400
 
401
		$key = Inflector::underscore(str_replace(array(DS, '/', '.', '<', '>', '?', ':', '|', '*', '"'), '_', strval($key)));
402
		return $key;
403
	}
404
 
405
/**
406
 * Recursively deletes all files under any directory named as $group
407
 *
408
 * @return boolean success
409
 */
410
	public function clearGroup($group) {
411
		$this->_File = null;
412
		$directoryIterator = new RecursiveDirectoryIterator($this->settings['path']);
413
		$contents = new RecursiveIteratorIterator($directoryIterator, RecursiveIteratorIterator::CHILD_FIRST);
414
		foreach ($contents as $object) {
415
			$containsGroup = strpos($object->getPathName(), DS . $group . DS) !== false;
416
			$hasPrefix = true;
417
			if (strlen($this->settings['prefix']) !== 0) {
418
				$hasPrefix = strpos($object->getBaseName(), $this->settings['prefix']) === 0;
419
			}
420
			if ($object->isFile() && $containsGroup && $hasPrefix) {
421
				$path = $object->getPathName();
422
				$object = null;
423
				//@codingStandardsIgnoreStart
424
				@unlink($path);
425
				//@codingStandardsIgnoreEnd
426
			}
427
		}
428
		return true;
429
	}
430
}