1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
21:
22: 23: 24: 25: 26: 27: 28: 29: 30:
31: class FileEngine extends CacheEngine {
32:
33: 34: 35: 36: 37:
38: protected $_File = null;
39:
40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50:
51: public $settings = array();
52:
53: 54: 55: 56: 57:
58: protected $_init = true;
59:
60: 61: 62: 63: 64: 65: 66: 67: 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: 95: 96: 97: 98:
99: public function gc($expires = null) {
100: return $this->clear(true);
101: }
102:
103: 104: 105: 106: 107: 108: 109: 110:
111: public function write($key, $data, $duration) {
112: if (!$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 = implode(array($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: 153: 154: 155: 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: 201: 202: 203: 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:
213: return @unlink($path);
214:
215: }
216:
217: 218: 219: 220: 221: 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: 256: 257: 258: 259: 260: 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: if ($dir === false) {
271: return;
272: }
273:
274: while (($entry = $dir->read()) !== false) {
275: if (substr($entry, 0, $prefixLength) !== $this->settings['prefix']) {
276: continue;
277: }
278:
279: try {
280: $file = new SplFileObject($path . $entry, 'r');
281: } catch (Exception $e) {
282: continue;
283: }
284:
285: if ($threshold) {
286: $mtime = $file->getMTime();
287:
288: if ($mtime > $threshold) {
289: continue;
290: }
291: $expires = (int)$file->current();
292:
293: if ($expires > $now) {
294: continue;
295: }
296: }
297: if ($file->isFile()) {
298: $filePath = $file->getRealPath();
299: $file = null;
300:
301:
302: @unlink($filePath);
303:
304: }
305: }
306: }
307:
308: 309: 310: 311: 312: 313: 314: 315:
316: public function decrement($key, $offset = 1) {
317: throw new CacheException(__d('cake_dev', 'Files cannot be atomically decremented.'));
318: }
319:
320: 321: 322: 323: 324: 325: 326: 327:
328: public function increment($key, $offset = 1) {
329: throw new CacheException(__d('cake_dev', 'Files cannot be atomically incremented.'));
330: }
331:
332: 333: 334: 335: 336: 337: 338: 339:
340: protected function _setKey($key, $createKey = false) {
341: $groups = null;
342: if (!empty($this->_groupPrefix)) {
343: $groups = vsprintf($this->_groupPrefix, $this->groups());
344: }
345: $dir = $this->settings['path'] . $groups;
346:
347: if (!is_dir($dir)) {
348: mkdir($dir, 0775, true);
349: }
350: $path = new SplFileInfo($dir . $key);
351:
352: if (!$createKey && !$path->isFile()) {
353: return false;
354: }
355: if (
356: empty($this->_File) ||
357: $this->_File->getBaseName() !== $key ||
358: $this->_File->valid() === false
359: ) {
360: $exists = file_exists($path->getPathname());
361: try {
362: $this->_File = $path->openFile('c+');
363: } catch (Exception $e) {
364: trigger_error($e->getMessage(), E_USER_WARNING);
365: return false;
366: }
367: unset($path);
368:
369: if (!$exists && !chmod($this->_File->getPathname(), (int)$this->settings['mask'])) {
370: trigger_error(__d(
371: 'cake_dev', 'Could not apply permission mask "%s" on cache file "%s"',
372: array($this->_File->getPathname(), $this->settings['mask'])), E_USER_WARNING);
373: }
374: }
375: return true;
376: }
377:
378: 379: 380: 381: 382:
383: protected function _active() {
384: $dir = new SplFileInfo($this->settings['path']);
385: if (Configure::read('debug')) {
386: $path = $dir->getPathname();
387: if (!is_dir($path)) {
388: mkdir($path, 0775, true);
389: }
390: }
391: if ($this->_init && !($dir->isDir() && $dir->isWritable())) {
392: $this->_init = false;
393: trigger_error(__d('cake_dev', '%s is not writable', $this->settings['path']), E_USER_WARNING);
394: return false;
395: }
396: return true;
397: }
398:
399: 400: 401: 402: 403: 404:
405: public function key($key) {
406: if (empty($key)) {
407: return false;
408: }
409:
410: $key = Inflector::underscore(str_replace(array(DS, '/', '.', '<', '>', '?', ':', '|', '*', '"'), '_', strval($key)));
411: return $key;
412: }
413:
414: 415: 416: 417: 418: 419:
420: public function clearGroup($group) {
421: $this->_File = null;
422: $directoryIterator = new RecursiveDirectoryIterator($this->settings['path']);
423: $contents = new RecursiveIteratorIterator($directoryIterator, RecursiveIteratorIterator::CHILD_FIRST);
424: foreach ($contents as $object) {
425: $containsGroup = strpos($object->getPathName(), DS . $group . DS) !== false;
426: $hasPrefix = true;
427: if (strlen($this->settings['prefix']) !== 0) {
428: $hasPrefix = strpos($object->getBaseName(), $this->settings['prefix']) === 0;
429: }
430: if ($object->isFile() && $containsGroup && $hasPrefix) {
431: $path = $object->getPathName();
432: $object = null;
433:
434: @unlink($path);
435:
436: }
437: }
438: return true;
439: }
440:
441: 442: 443: 444: 445: 446: 447: 448: 449:
450: public function add($key, $value, $duration) {
451: $cachedValue = $this->read($key);
452: if ($cachedValue === false) {
453: return $this->write($key, $value, $duration);
454: }
455: return false;
456: }
457: }
458: