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: 32:
33: class FileEngine extends CacheEngine {
34:
35: 36: 37: 38: 39:
40: protected $_File = null;
41:
42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52:
53: public $settings = array();
54:
55: 56: 57: 58: 59:
60: protected $_init = true;
61:
62: 63: 64: 65: 66: 67: 68: 69: 70:
71: public function init($settings = array()) {
72: $settings += array(
73: 'engine' => 'File',
74: 'path' => CACHE,
75: 'prefix' => 'cake_',
76: 'lock' => true,
77: 'serialize' => true,
78: 'isWindows' => false,
79: 'mask' => 0664
80: );
81: parent::init($settings);
82:
83: if (DS === '\\') {
84: $this->settings['isWindows'] = true;
85: }
86: if (substr($this->settings['path'], -1) !== DS) {
87: $this->settings['path'] .= DS;
88: }
89: if (!empty($this->_groupPrefix)) {
90: $this->_groupPrefix = str_replace('_', DS, $this->_groupPrefix);
91: }
92: return $this->_active();
93: }
94:
95: 96: 97: 98: 99: 100:
101: public function gc($expires = null) {
102: return $this->clear(true);
103: }
104:
105: 106: 107: 108: 109: 110: 111: 112:
113: public function write($key, $data, $duration) {
114: if ($data === '' || !$this->_init) {
115: return false;
116: }
117:
118: if ($this->_setKey($key, true) === false) {
119: return false;
120: }
121:
122: $lineBreak = "\n";
123:
124: if ($this->settings['isWindows']) {
125: $lineBreak = "\r\n";
126: }
127:
128: if (!empty($this->settings['serialize'])) {
129: if ($this->settings['isWindows']) {
130: $data = str_replace('\\', '\\\\\\\\', serialize($data));
131: } else {
132: $data = serialize($data);
133: }
134: }
135:
136: $expires = time() + $duration;
137: $contents = $expires . $lineBreak . $data . $lineBreak;
138:
139: if ($this->settings['lock']) {
140: $this->_File->flock(LOCK_EX);
141: }
142:
143: $this->_File->rewind();
144: $success = $this->_File->ftruncate(0) && $this->_File->fwrite($contents) && $this->_File->fflush();
145:
146: if ($this->settings['lock']) {
147: $this->_File->flock(LOCK_UN);
148: }
149:
150: return $success;
151: }
152:
153: 154: 155: 156: 157: 158:
159: public function read($key) {
160: if (!$this->_init || $this->_setKey($key) === false) {
161: return false;
162: }
163:
164: if ($this->settings['lock']) {
165: $this->_File->flock(LOCK_SH);
166: }
167:
168: $this->_File->rewind();
169: $time = time();
170: $cachetime = intval($this->_File->current());
171:
172: if ($cachetime !== false && ($cachetime < $time || ($time + $this->settings['duration']) < $cachetime)) {
173: if ($this->settings['lock']) {
174: $this->_File->flock(LOCK_UN);
175: }
176: return false;
177: }
178:
179: $data = '';
180: $this->_File->next();
181: while ($this->_File->valid()) {
182: $data .= $this->_File->current();
183: $this->_File->next();
184: }
185:
186: if ($this->settings['lock']) {
187: $this->_File->flock(LOCK_UN);
188: }
189:
190: $data = trim($data);
191:
192: if ($data !== '' && !empty($this->settings['serialize'])) {
193: if ($this->settings['isWindows']) {
194: $data = str_replace('\\\\\\\\', '\\', $data);
195: }
196: $data = unserialize((string)$data);
197: }
198: return $data;
199: }
200:
201: 202: 203: 204: 205: 206:
207: public function delete($key) {
208: if ($this->_setKey($key) === false || !$this->_init) {
209: return false;
210: }
211: $path = $this->_File->getRealPath();
212: $this->_File = null;
213: return unlink($path);
214: }
215:
216: 217: 218: 219: 220: 221:
222: public function clear($check) {
223: if (!$this->_init) {
224: return false;
225: }
226: $this->_File = null;
227:
228: $threshold = $now = false;
229: if ($check) {
230: $now = time();
231: $threshold = $now - $this->settings['duration'];
232: }
233:
234: $this->_clearDirectory($this->settings['path'], $now, $threshold);
235:
236: $directory = new RecursiveDirectoryIterator($this->settings['path']);
237: $contents = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
238: $cleared = array();
239: foreach ($contents as $path) {
240: if ($path->isFile()) {
241: continue;
242: }
243:
244: $path = $path->getRealPath() . DS;
245: if (!in_array($path, $cleared)) {
246: $this->_clearDirectory($path, $now, $threshold);
247: $cleared[] = $path;
248: }
249: }
250: return true;
251: }
252:
253: 254: 255: 256: 257: 258: 259: 260:
261: protected function _clearDirectory($path, $now, $threshold) {
262: $prefixLength = strlen($this->settings['prefix']);
263:
264: if (!is_dir($path)) {
265: return;
266: }
267:
268: $dir = dir($path);
269: while (($entry = $dir->read()) !== false) {
270: if (substr($entry, 0, $prefixLength) !== $this->settings['prefix']) {
271: continue;
272: }
273: $filePath = $path . $entry;
274: if (!file_exists($filePath) || is_dir($filePath)) {
275: continue;
276: }
277: $file = new SplFileObject($path . $entry, 'r');
278:
279: if ($threshold) {
280: $mtime = $file->getMTime();
281:
282: if ($mtime > $threshold) {
283: continue;
284: }
285: $expires = (int)$file->current();
286:
287: if ($expires > $now) {
288: continue;
289: }
290: }
291: if ($file->isFile()) {
292: $_path = $file->getRealPath();
293: $file = null;
294: unlink($_path);
295: }
296: }
297: }
298:
299: 300: 301: 302: 303: 304: 305: 306:
307: public function decrement($key, $offset = 1) {
308: throw new CacheException(__d('cake_dev', 'Files cannot be atomically decremented.'));
309: }
310:
311: 312: 313: 314: 315: 316: 317: 318:
319: public function increment($key, $offset = 1) {
320: throw new CacheException(__d('cake_dev', 'Files cannot be atomically incremented.'));
321: }
322:
323: 324: 325: 326: 327: 328: 329: 330:
331: protected function _setKey($key, $createKey = false) {
332: $groups = null;
333: if (!empty($this->_groupPrefix)) {
334: $groups = vsprintf($this->_groupPrefix, $this->groups());
335: }
336: $dir = $this->settings['path'] . $groups;
337:
338: if (!is_dir($dir)) {
339: mkdir($dir, 0777, true);
340: }
341: $path = new SplFileInfo($dir . $key);
342:
343: if (!$createKey && !$path->isFile()) {
344: return false;
345: }
346: if (empty($this->_File) || $this->_File->getBaseName() !== $key) {
347: $exists = file_exists($path->getPathname());
348: try {
349: $this->_File = $path->openFile('c+');
350: } catch (Exception $e) {
351: trigger_error($e->getMessage(), E_USER_WARNING);
352: return false;
353: }
354: unset($path);
355:
356: if (!$exists && !chmod($this->_File->getPathname(), (int)$this->settings['mask'])) {
357: trigger_error(__d(
358: 'cake_dev', 'Could not apply permission mask "%s" on cache file "%s"',
359: array($this->_File->getPathname(), $this->settings['mask'])), E_USER_WARNING);
360: }
361: }
362: return true;
363: }
364:
365: 366: 367: 368: 369:
370: protected function _active() {
371: $dir = new SplFileInfo($this->settings['path']);
372: if ($this->_init && !($dir->isDir() && $dir->isWritable())) {
373: $this->_init = false;
374: trigger_error(__d('cake_dev', '%s is not writable', $this->settings['path']), E_USER_WARNING);
375: return false;
376: }
377: return true;
378: }
379:
380: 381: 382: 383: 384: 385:
386: public function key($key) {
387: if (empty($key)) {
388: return false;
389: }
390:
391: $key = Inflector::underscore(str_replace(array(DS, '/', '.', '<', '>', '?', ':', '|', '*', '"'), '_', strval($key)));
392: return $key;
393: }
394:
395: 396: 397: 398: 399:
400: public function clearGroup($group) {
401: $this->_File = null;
402: $directoryIterator = new RecursiveDirectoryIterator($this->settings['path']);
403: $contents = new RecursiveIteratorIterator($directoryIterator, RecursiveIteratorIterator::CHILD_FIRST);
404: foreach ($contents as $object) {
405: $containsGroup = strpos($object->getPathName(), DS . $group . DS) !== false;
406: $hasPrefix = strpos($object->getBaseName(), $this->settings['prefix']) === 0;
407: if ($object->isFile() && $containsGroup && $hasPrefix) {
408: unlink($object->getPathName());
409: }
410: }
411: return true;
412: }
413: }
414: