Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
16591 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 bool
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 bool 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 int $expires [optional] An expires timestamp, invalidating all data before.
97
 * @return bool 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 int $duration How long to cache the data, in seconds
109
 * @return bool 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 = (int)$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 bool 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 bool $check Optional - only delete expired cache items
221
 * @return bool 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 int $now The current timestamp
259
 * @param int $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
 
275
			try {
276
				$file = new SplFileObject($path . $entry, 'r');
277
			} catch (Exception $e) {
278
				continue;
279
			}
280
 
281
			if ($threshold) {
282
				$mtime = $file->getMTime();
283
 
284
				if ($mtime > $threshold) {
285
					continue;
286
				}
287
				$expires = (int)$file->current();
288
 
289
				if ($expires > $now) {
290
					continue;
291
				}
292
			}
293
			if ($file->isFile()) {
294
				$filePath = $file->getRealPath();
295
				$file = null;
296
 
297
				//@codingStandardsIgnoreStart
298
				@unlink($filePath);
299
				//@codingStandardsIgnoreEnd
300
			}
301
		}
302
	}
303
 
304
/**
305
 * Not implemented
306
 *
307
 * @param string $key The key to decrement
308
 * @param int $offset The number to offset
309
 * @return void
310
 * @throws CacheException
311
 */
312
	public function decrement($key, $offset = 1) {
313
		throw new CacheException(__d('cake_dev', 'Files cannot be atomically decremented.'));
314
	}
315
 
316
/**
317
 * Not implemented
318
 *
319
 * @param string $key The key to decrement
320
 * @param int $offset The number to offset
321
 * @return void
322
 * @throws CacheException
323
 */
324
	public function increment($key, $offset = 1) {
325
		throw new CacheException(__d('cake_dev', 'Files cannot be atomically incremented.'));
326
	}
327
 
328
/**
329
 * Sets the current cache key this class is managing, and creates a writable SplFileObject
330
 * for the cache file the key is referring to.
331
 *
332
 * @param string $key The key
333
 * @param bool $createKey Whether the key should be created if it doesn't exists, or not
334
 * @return bool true if the cache key could be set, false otherwise
335
 */
336
	protected function _setKey($key, $createKey = false) {
337
		$groups = null;
338
		if (!empty($this->_groupPrefix)) {
339
			$groups = vsprintf($this->_groupPrefix, $this->groups());
340
		}
341
		$dir = $this->settings['path'] . $groups;
342
 
343
		if (!is_dir($dir)) {
344
			mkdir($dir, 0775, true);
345
		}
346
		$path = new SplFileInfo($dir . $key);
347
 
348
		if (!$createKey && !$path->isFile()) {
349
			return false;
350
		}
351
		if (empty($this->_File) || $this->_File->getBaseName() !== $key) {
352
			$exists = file_exists($path->getPathname());
353
			try {
354
				$this->_File = $path->openFile('c+');
355
			} catch (Exception $e) {
356
				trigger_error($e->getMessage(), E_USER_WARNING);
357
				return false;
358
			}
359
			unset($path);
360
 
361
			if (!$exists && !chmod($this->_File->getPathname(), (int)$this->settings['mask'])) {
362
				trigger_error(__d(
363
					'cake_dev', 'Could not apply permission mask "%s" on cache file "%s"',
364
					array($this->_File->getPathname(), $this->settings['mask'])), E_USER_WARNING);
365
			}
366
		}
367
		return true;
368
	}
369
 
370
/**
371
 * Determine is cache directory is writable
372
 *
373
 * @return bool
374
 */
375
	protected function _active() {
376
		$dir = new SplFileInfo($this->settings['path']);
377
		if (Configure::read('debug')) {
378
			$path = $dir->getPathname();
379
			if (!is_dir($path)) {
380
				mkdir($path, 0775, true);
381
			}
382
		}
383
		if ($this->_init && !($dir->isDir() && $dir->isWritable())) {
384
			$this->_init = false;
385
			trigger_error(__d('cake_dev', '%s is not writable', $this->settings['path']), E_USER_WARNING);
386
			return false;
387
		}
388
		return true;
389
	}
390
 
391
/**
392
 * Generates a safe key for use with cache engine storage engines.
393
 *
394
 * @param string $key the key passed over
395
 * @return mixed string $key or false
396
 */
397
	public function key($key) {
398
		if (empty($key)) {
399
			return false;
400
		}
401
 
402
		$key = Inflector::underscore(str_replace(array(DS, '/', '.', '<', '>', '?', ':', '|', '*', '"'), '_', strval($key)));
403
		return $key;
404
	}
405
 
406
/**
407
 * Recursively deletes all files under any directory named as $group
408
 *
409
 * @param string $group The group to clear.
410
 * @return bool success
411
 */
412
	public function clearGroup($group) {
413
		$this->_File = null;
414
		$directoryIterator = new RecursiveDirectoryIterator($this->settings['path']);
415
		$contents = new RecursiveIteratorIterator($directoryIterator, RecursiveIteratorIterator::CHILD_FIRST);
416
		foreach ($contents as $object) {
417
			$containsGroup = strpos($object->getPathName(), DS . $group . DS) !== false;
418
			$hasPrefix = true;
419
			if (strlen($this->settings['prefix']) !== 0) {
420
				$hasPrefix = strpos($object->getBaseName(), $this->settings['prefix']) === 0;
421
			}
422
			if ($object->isFile() && $containsGroup && $hasPrefix) {
423
				$path = $object->getPathName();
424
				$object = null;
425
				//@codingStandardsIgnoreStart
426
				@unlink($path);
427
				//@codingStandardsIgnoreEnd
428
			}
429
		}
430
		return true;
431
	}
432
}