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: 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:
298: @unlink($filePath);
299:
300: }
301: }
302: }
303:
304: 305: 306: 307: 308: 309: 310: 311:
312: public function decrement($key, $offset = 1) {
313: throw new CacheException(__d('cake_dev', 'Files cannot be atomically decremented.'));
314: }
315:
316: 317: 318: 319: 320: 321: 322: 323:
324: public function increment($key, $offset = 1) {
325: throw new CacheException(__d('cake_dev', 'Files cannot be atomically incremented.'));
326: }
327:
328: 329: 330: 331: 332: 333: 334: 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: 372: 373: 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: 393: 394: 395: 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: 408: 409: 410: 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:
426: @unlink($path);
427:
428: }
429: }
430: return true;
431: }
432:
433: 434: 435: 436: 437: 438: 439: 440: 441:
442: public function add($key, $value, $duration) {
443: $cachedValue = $this->read($key);
444: if ($cachedValue === false) {
445: return $this->write($key, $value, $duration);
446: }
447: return false;
448: }
449: }
450: