1: <?php
2: /* SVN FILE: $Id$ */
3: /**
4: * File Storage engine for cache
5: *
6: *
7: * PHP versions 4 and 5
8: *
9: * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
10: * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
11: *
12: * Licensed under The MIT License
13: * Redistributions of files must retain the above copyright notice.
14: *
15: * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
16: * @link http://cakephp.org CakePHP(tm) Project
17: * @package cake
18: * @subpackage cake.cake.libs.cache
19: * @since CakePHP(tm) v 1.2.0.4933
20: * @version $Revision$
21: * @modifiedby $LastChangedBy$
22: * @lastmodified $Date$
23: * @license http://www.opensource.org/licenses/mit-license.php The MIT License
24: */
25: /**
26: * File Storage engine for cache
27: *
28: * @todo use the File and Folder classes (if it's not a too big performance hit)
29: * @package cake
30: * @subpackage cake.cake.libs.cache
31: */
32: class FileEngine extends CacheEngine {
33: /**
34: * Instance of File class
35: *
36: * @var File
37: * @access private
38: */
39: var $__File = null;
40: /**
41: * settings
42: * path = absolute path to cache directory, default => CACHE
43: * prefix = string prefix for filename, default => cake_
44: * lock = enable file locking on write, default => false
45: * serialize = serialize the data, default => true
46: *
47: * @var array
48: * @see CacheEngine::__defaults
49: * @access public
50: */
51: var $settings = array();
52: /**
53: * Set to true if FileEngine::init(); and FileEngine::__active(); do not fail.
54: *
55: * @var boolean
56: * @access private
57: */
58: var $__active = false;
59: /**
60: * True unless FileEngine::__active(); fails
61: *
62: * @var boolean
63: * @access private
64: */
65: var $__init = true;
66: /**
67: * Initialize the Cache Engine
68: *
69: * Called automatically by the cache frontend
70: * To reinitialize the settings call Cache::engine('EngineName', [optional] settings = array());
71: *
72: * @param array $setting array of setting for the engine
73: * @return boolean True if the engine has been successfully initialized, false if not
74: * @access public
75: */
76: function init($settings = array()) {
77: parent::init(array_merge(
78: array(
79: 'engine' => 'File', 'path' => CACHE, 'prefix'=> 'cake_', 'lock'=> false,
80: 'serialize'=> true, 'isWindows' => false
81: ),
82: $settings
83: ));
84: if (!isset($this->__File)) {
85: if (!class_exists('File')) {
86: require LIBS . 'file.php';
87: }
88: $this->__File =& new File($this->settings['path'] . DS . 'cake');
89: }
90:
91: if (DIRECTORY_SEPARATOR === '\\') {
92: $this->settings['isWindows'] = true;
93: }
94:
95: $this->settings['path'] = $this->__File->Folder->cd($this->settings['path']);
96: if (empty($this->settings['path'])) {
97: return false;
98: }
99: return $this->__active();
100: }
101: /**
102: * Garbage collection. Permanently remove all expired and deleted data
103: *
104: * @return boolean True if garbage collection was succesful, false on failure
105: * @access public
106: */
107: function gc() {
108: return $this->clear(true);
109: }
110: /**
111: * Write data for key into cache
112: *
113: * @param string $key Identifier for the data
114: * @param mixed $data Data to be cached
115: * @param mixed $duration How long to cache the data, in seconds
116: * @return boolean True if the data was succesfully cached, false on failure
117: * @access public
118: */
119: function write($key, &$data, $duration) {
120: if ($data === '' || !$this->__init) {
121: return false;
122: }
123:
124: if ($this->__setKey($key) === false) {
125: return false;
126: }
127:
128: $lineBreak = "\n";
129:
130: if ($this->settings['isWindows']) {
131: $lineBreak = "\r\n";
132: }
133:
134: if (!empty($this->settings['serialize'])) {
135: if ($this->settings['isWindows']) {
136: $data = str_replace('\\', '\\\\\\\\', serialize($data));
137: } else {
138: $data = serialize($data);
139: }
140: }
141:
142: $expires = time() + $duration;
143: $contents = $expires . $lineBreak . $data . $lineBreak;
144: $old = umask(0);
145: $handle = fopen($this->__File->path, 'a');
146: umask($old);
147:
148: if (!$handle) {
149: return false;
150: }
151:
152: if ($this->settings['lock']) {
153: flock($handle, LOCK_EX);
154: }
155:
156: $success = ftruncate($handle, 0) && fwrite($handle, $contents) && fflush($handle);
157:
158: if ($this->settings['lock']) {
159: flock($handle, LOCK_UN);
160: }
161:
162: fclose($handle);
163: return $success;
164: }
165: /**
166: * Read a key from the cache
167: *
168: * @param string $key Identifier for the data
169: * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it
170: * @access public
171: */
172: function read($key) {
173: if ($this->__setKey($key) === false || !$this->__init || !$this->__File->exists()) {
174: return false;
175: }
176: if ($this->settings['lock']) {
177: $this->__File->lock = true;
178: }
179: $time = time();
180: $cachetime = intval($this->__File->read(11));
181:
182: if ($cachetime !== false && ($cachetime < $time || ($time + $this->settings['duration']) < $cachetime)) {
183: $this->__File->close();
184: return false;
185: }
186: $data = $this->__File->read(true);
187:
188: if ($data !== '' && !empty($this->settings['serialize'])) {
189: if ($this->settings['isWindows']) {
190: $data = str_replace('\\\\\\\\', '\\', $data);
191: }
192: $data = unserialize((string)$data);
193: }
194: $this->__File->close();
195: return $data;
196: }
197: /**
198: * Delete a key from the cache
199: *
200: * @param string $key Identifier for the data
201: * @return boolean True if the value was successfully deleted, false if it didn't exist or couldn't be removed
202: * @access public
203: */
204: function delete($key) {
205: if ($this->__setKey($key) === false || !$this->__init) {
206: return false;
207: }
208: return $this->__File->delete();
209: }
210: /**
211: * Delete all values from the cache
212: *
213: * @param boolean $check Optional - only delete expired cache items
214: * @return boolean True if the cache was succesfully cleared, false otherwise
215: * @access public
216: */
217: function clear($check) {
218: if (!$this->__init) {
219: return false;
220: }
221: $dir = dir($this->settings['path']);
222: if ($check) {
223: $now = time();
224: $threshold = $now - $this->settings['duration'];
225: }
226: while (($entry = $dir->read()) !== false) {
227: if ($this->__setKey($entry) === false) {
228: continue;
229: }
230: if ($check) {
231: $mtime = $this->__File->lastChange();
232:
233: if ($mtime === false || $mtime > $threshold) {
234: continue;
235: }
236:
237: $expires = $this->__File->read(11);
238: $this->__File->close();
239:
240: if ($expires > $now) {
241: continue;
242: }
243: }
244: $this->__File->delete();
245: }
246: $dir->close();
247: return true;
248: }
249: /**
250: * Get absolute file for a given key
251: *
252: * @param string $key The key
253: * @return mixed Absolute cache file for the given key or false if erroneous
254: * @access private
255: */
256: function __setKey($key) {
257: $this->__File->Folder->cd($this->settings['path']);
258: if ($key !== $this->__File->name) {
259: $this->__File->name = $key;
260: $this->__File->path = null;
261: }
262: if (!$this->__File->Folder->inPath($this->__File->pwd(), true)) {
263: return false;
264: }
265: }
266: /**
267: * Determine is cache directory is writable
268: *
269: * @return boolean
270: * @access private
271: */
272: function __active() {
273: if (!$this->__active && $this->__init && !is_writable($this->settings['path'])) {
274: $this->__init = false;
275: trigger_error(sprintf(__('%s is not writable', true), $this->settings['path']), E_USER_WARNING);
276: } else {
277: $this->__active = true;
278: }
279: return true;
280: }
281: }
282: ?>
283: