FileEngine.php 7.3 KB

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