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 ($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: 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 = 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: 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: $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:
297: @unlink($filePath);
298:
299: }
300: }
301: }
302:
303: 304: 305: 306: 307: 308: 309: 310:
311: public function decrement($key, $offset = 1) {
312: throw new CacheException(__d('cake_dev', 'Files cannot be atomically decremented.'));
313: }
314:
315: 316: 317: 318: 319: 320: 321: 322:
323: public function increment($key, $offset = 1) {
324: throw new CacheException(__d('cake_dev', 'Files cannot be atomically incremented.'));
325: }
326:
327: 328: 329: 330: 331: 332: 333: 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: 371: 372: 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: 392: 393: 394: 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: 407: 408: 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:
424: @unlink($path);
425:
426: }
427: }
428: return true;
429: }
430: }
431: