W3TCCache_File

Class Cache_File.

Defined (1)

The class is defined in the following location(s).

/Cache_File.php  
  1. class Cache_File extends Cache_Base { 
  2. /** 
  3. * Path to cache dir 
  4. * @var string 
  5. */ 
  6. protected $_cache_dir = ''; 
  7.  
  8. /** 
  9. * Directory to flush 
  10. * @var string 
  11. */ 
  12. protected $_flush_dir = ''; 
  13. /** 
  14. * Exclude files 
  15. * @var array 
  16. */ 
  17. protected $_exclude = array(); 
  18.  
  19. /** 
  20. * Flush time limit 
  21. * @var int 
  22. */ 
  23. protected $_flush_timelimit = 0; 
  24.  
  25. /** 
  26. * File locking 
  27. * @var boolean 
  28. */ 
  29. protected $_locking = false; 
  30.  
  31. /** 
  32. * If path should be generated based on wp_hash 
  33. * @var bool 
  34. */ 
  35. protected $_use_wp_hash = false; 
  36.  
  37. /** 
  38. * Constructor 
  39. * @param array $config 
  40. */ 
  41. function __construct( $config = array() ) { 
  42. parent::__construct( $config ); 
  43. if ( isset( $config['cache_dir'] ) ) 
  44. $this->_cache_dir = trim( $config['cache_dir'] ); 
  45. else 
  46. $this->_cache_dir = Util_Environment::cache_blog_dir( $config['section'], $config['blog_id'] ); 
  47.  
  48. $this->_exclude = isset( $config['exclude'] ) ? (array) $config['exclude'] : array(); 
  49. $this->_flush_timelimit = isset( $config['flush_timelimit'] ) ? (int) $config['flush_timelimit'] : 180; 
  50. $this->_locking = isset( $config['locking'] ) ? (boolean) $config['locking'] : false; 
  51.  
  52. if ( isset( $config['flush_dir'] ) ) 
  53. $this->_flush_dir = $config['flush_dir']; 
  54. else { 
  55. if ( $config['blog_id'] <= 0 && !isset( $config['cache_dir'] ) ) { 
  56. // clear whole section if we operate on master cache 
  57. // and in a mode when cache_dir not strictly specified 
  58. $this->_flush_dir = Util_Environment::cache_dir( $config['section'] ); 
  59. } else 
  60. $this->_flush_dir = $this->_cache_dir; 
  61. if ( isset( $config['use_wp_hash'] ) && $config['use_wp_hash'] ) 
  62. $this->_use_wp_hash = true; 
  63.  
  64. /** 
  65. * Adds data 
  66. * @param string $key 
  67. * @param mixed $var 
  68. * @param integer $expire 
  69. * @param string $group Used to differentiate between groups of cache values 
  70. * @return boolean 
  71. */ 
  72. function add( $key, &$var, $expire = 0, $group = '' ) { 
  73. if ( $this->get( $key, $group ) === false ) { 
  74. return $this->set( $key, $var, $expire, $group ); 
  75.  
  76. return false; 
  77.  
  78. /** 
  79. * Sets data 
  80. * @param string $key 
  81. * @param mixed $var 
  82. * @param integer $expire 
  83. * @param string $group Used to differentiate between groups of cache values 
  84. * @return boolean 
  85. */ 
  86. function set( $key, $var, $expire = 0, $group = '' ) { 
  87. $fp = $this->fopen_write( $key, $group, 'wb' ); 
  88. if ( !$fp ) 
  89. return false; 
  90.  
  91. if ( $this->_locking ) 
  92. @flock( $fp, LOCK_EX ); 
  93.  
  94. if ( $expire <= 0 || $expire > W3TC_CACHE_FILE_EXPIRE_MAX ) 
  95. $expire = W3TC_CACHE_FILE_EXPIRE_MAX; 
  96.  
  97. $expires_at = time() + $expire; 
  98. @fputs( $fp, pack( 'L', $expires_at ) ); 
  99. @fputs( $fp, '<?php exit; ?>' ); 
  100. @fputs( $fp, @serialize( $var ) ); 
  101. @fclose( $fp ); 
  102.  
  103. if ( $this->_locking ) 
  104. @flock( $fp, LOCK_UN ); 
  105.  
  106. return true; 
  107.  
  108. /** 
  109. * Returns data 
  110. * @param string $key 
  111. * @param string $group Used to differentiate between groups of cache values 
  112. * @return mixed 
  113. */ 
  114. function get_with_old( $key, $group = '' ) { 
  115. list( $data, $has_old_data ) = $this->_get_with_old_raw( $key, $group ); 
  116. if ( !empty( $data ) ) 
  117. $data_unserialized = @unserialize( $data ); 
  118. else 
  119. $data_unserialized = $data; 
  120.  
  121. return array( $data_unserialized, $has_old_data ); 
  122.  
  123.  
  124.  
  125. private function _get_with_old_raw( $key, $group = '' ) { 
  126. $has_old_data = false; 
  127.  
  128. $storage_key = $this->get_item_key( $key ); 
  129.  
  130. $path = $this->_cache_dir . DIRECTORY_SEPARATOR . 
  131. ( $group ? $group . DIRECTORY_SEPARATOR : '' ) . 
  132. $this->_get_path( $storage_key ); 
  133. if ( !is_readable( $path ) ) 
  134. return array( null, $has_old_data ); 
  135.  
  136. $fp = @fopen( $path, 'rb' ); 
  137. if ( !$fp ) 
  138. return array( null, $has_old_data ); 
  139.  
  140. if ( $this->_locking ) 
  141. @flock( $fp, LOCK_SH ); 
  142.  
  143. $expires_at = @fread( $fp, 4 ); 
  144. $data = null; 
  145.  
  146. if ( $expires_at !== false ) { 
  147. list( , $expires_at ) = @unpack( 'L', $expires_at ); 
  148.  
  149. if ( time() > $expires_at ) { 
  150. if ( $this->_use_expired_data ) { 
  151. // update expiration so other threads will use old data 
  152. $fp2 = @fopen( $path, 'cb' ); 
  153.  
  154. if ( $fp2 ) { 
  155. @fputs( $fp2, pack( 'L', time() + 30 ) ); 
  156. @fclose( $fp2 ); 
  157. $has_old_data = true; 
  158. } else { 
  159. $data = ''; 
  160.  
  161. while ( !@feof( $fp ) ) { 
  162. $data .= @fread( $fp, 4096 ); 
  163. $data = substr( $data, 14 ); 
  164.  
  165.  
  166. if ( $this->_locking ) 
  167. @flock( $fp, LOCK_UN ); 
  168.  
  169. @fclose( $fp ); 
  170.  
  171. return array( $data, $has_old_data ); 
  172.  
  173. /** 
  174. * Replaces data 
  175. * @param string $key 
  176. * @param mixed $var 
  177. * @param integer $expire 
  178. * @param string $group Used to differentiate between groups of cache values 
  179. * @return boolean 
  180. */ 
  181. function replace( $key, &$var, $expire = 0, $group = '' ) { 
  182. if ( $this->get( $key, $group ) !== false ) { 
  183. return $this->set( $key, $var, $expire, $group ); 
  184.  
  185. return false; 
  186.  
  187. /** 
  188. * Deletes data 
  189. * @param string $key 
  190. * @param string $group Used to differentiate between groups of cache values 
  191. * @return boolean 
  192. */ 
  193. function delete( $key, $group = '' ) { 
  194. $storage_key = $this->get_item_key( $key ); 
  195.  
  196. $path = $this->_cache_dir . DIRECTORY_SEPARATOR . 
  197. ( $group ? $group . DIRECTORY_SEPARATOR : '' ) . 
  198. $this->_get_path( $storage_key ); 
  199.  
  200. if ( !file_exists( $path ) ) 
  201. return true; 
  202.  
  203. if ( $this->_use_expired_data ) { 
  204. $fp = @fopen( $path, 'cb' ); 
  205.  
  206. if ( $fp ) { 
  207. if ( $this->_locking ) 
  208. @flock( $fp, LOCK_EX ); 
  209.  
  210. @fputs( $fp, pack( 'L', 0 ) ); // make it expired 
  211. @fclose( $fp ); 
  212.  
  213. if ( $this->_locking ) 
  214. @flock( $fp, LOCK_UN ); 
  215. return true; 
  216.  
  217.  
  218. return @unlink( $path ); 
  219.  
  220. /** 
  221. * Deletes _old and primary if exists. 
  222. * @param string $key 
  223. * @return bool 
  224. */ 
  225. function hard_delete( $key ) { 
  226. $key = $this->get_item_key( $key ); 
  227. $path = $this->_cache_dir . DIRECTORY_SEPARATOR . $this->_get_path( $key ); 
  228. return @unlink( $path ); 
  229.  
  230. /** 
  231. * Flushes all data 
  232. * @param string $group Used to differentiate between groups of cache values 
  233. * @return boolean 
  234. */ 
  235. function flush( $group = '' ) { 
  236. @set_time_limit( $this->_flush_timelimit ); 
  237. $flush_dir = $group ? 
  238. $this->_cache_dir . DIRECTORY_SEPARATOR . $group . 
  239. DIRECTORY_SEPARATOR : 
  240. $this->_flush_dir; 
  241. Util_File::emptydir( $flush_dir, $this->_exclude ); 
  242. return true; 
  243.  
  244. /** 
  245. * Returns modification time of cache file 
  246. * @param integer $key 
  247. * @param string $group Used to differentiate between groups of cache values 
  248. * @return boolean|string 
  249. */ 
  250. function mtime( $key, $group = '' ) { 
  251. $path = 
  252. $this->_cache_dir . DIRECTORY_SEPARATOR . 
  253. ( $group ? $group . DIRECTORY_SEPARATOR : '' ) . 
  254. $this->_get_path( $key ); 
  255.  
  256. if ( file_exists( $path ) ) { 
  257. return @filemtime( $path ); 
  258.  
  259. return false; 
  260.  
  261. /** 
  262. * Returns file path for key 
  263. * @param string $key 
  264. * @return string 
  265. */ 
  266. function _get_path( $key ) { 
  267. if ( $this->_use_wp_hash && function_exists( 'wp_hash' ) ) 
  268. $hash = wp_hash( $key ); 
  269. else 
  270. $hash = md5( $key ); 
  271.  
  272. $path = sprintf( '%s/%s/%s.php', substr( $hash, 0, 3 ), substr( $hash, 3, 3 ), $hash ); 
  273.  
  274. return $path; 
  275.  
  276. public function get_stats_size( $timeout_time ) { 
  277. $size = array( 
  278. 'bytes' => 0,  
  279. 'items' => 0,  
  280. 'timeout_occurred' => false 
  281. ); 
  282.  
  283. $size = $this->dirsize( $this->_cache_dir, $size, $timeout_time ); 
  284. return $size; 
  285.  
  286.  
  287.  
  288. private function dirsize( $path, $size, $timeout_time ) { 
  289. $dir = @opendir( $path ); 
  290.  
  291. if ( $dir ) { 
  292. while ( !$size['timeout_occurred'] && ( $entry = @readdir( $dir ) ) !== false ) { 
  293. if ( $entry == '.' || $entry == '..' ) { 
  294. continue; 
  295.  
  296. $full_path = $path . DIRECTORY_SEPARATOR . $entry; 
  297.  
  298. if ( @is_dir( $full_path ) ) { 
  299. $size = $this->dirsize( $full_path, $size, $timeout_time ); 
  300. } else { 
  301. $size['bytes'] += @filesize( $full_path ); 
  302.  
  303. // dont check time() for each file, quite expensive 
  304. $size['items']++; 
  305. if ( $size['items'] % 1000 == 0 ) 
  306. $size['timeout_occurred'] |= ( time() > $timeout_time ); 
  307.  
  308. @closedir( $dir ); 
  309.  
  310. return $size; 
  311.  
  312. /** 
  313. * Used to replace as atomically as possible known value to new one 
  314. */ 
  315. public function set_if_maybe_equals( $key, $old_value, $new_value ) { 
  316. // cant guarantee atomic action here, filelocks fail often 
  317. $value = $this->get( $key ); 
  318. if ( isset( $old_value['content'] ) && 
  319. $value['content'] != $old_value['content'] ) 
  320. return false; 
  321.  
  322. return $this->set( $key, $new_value ); 
  323.  
  324. /** 
  325. * Use key as a counter and add integet value to it 
  326. */ 
  327. public function counter_add( $key, $value ) { 
  328. if ( $value == 0 ) 
  329. return true; 
  330.  
  331. $fp = $this->fopen_write( $key, '', 'a' ); 
  332. if ( !$fp ) 
  333. return false; 
  334.  
  335. // use "x" to store increment, since it's most often case 
  336. // and it will save 50% of size if only increments are used 
  337. if ( $value == 1 ) 
  338. @fputs( $fp, 'x' ); 
  339. else 
  340. @fputs( $fp, ' ' . (int)$value ); 
  341.  
  342. @fclose( $fp ); 
  343. return true; 
  344.  
  345. /** 
  346. * Use key as a counter and add integet value to it 
  347. */ 
  348. public function counter_set( $key, $value ) { 
  349. $fp = $this->fopen_write( $key, '', 'wb' ); 
  350. if ( !$fp ) 
  351. return false; 
  352.  
  353. $expire = W3TC_CACHE_FILE_EXPIRE_MAX; 
  354. $expires_at = time() + $expire; 
  355.  
  356. @fputs( $fp, pack( 'L', $expires_at ) ); 
  357. @fputs( $fp, '<?php exit; ?>' ); 
  358. @fputs( $fp, (int)$value ); 
  359. @fclose( $fp ); 
  360.  
  361. return true; 
  362.  
  363. /** 
  364. * Get counter's value 
  365. */ 
  366. public function counter_get( $key ) { 
  367. list( $value, $old_data ) = $this->_get_with_old_raw( $key ); 
  368. if ( empty( $value ) ) 
  369. return 0; 
  370.  
  371. $original_length = strlen( $value ); 
  372. $cut_value = str_replace( 'x', '', $value ); 
  373.  
  374. $count = $original_length - strlen( $cut_value ); 
  375.  
  376. // values more than 1 are stored as <space>value 
  377. $a = explode( ' ', $cut_value ); 
  378. foreach ( $a as $counter_value ) 
  379. $count += (int)$counter_value; 
  380.  
  381. return $count; 
  382.  
  383. private function fopen_write( $key, $group, $mode ) { 
  384. $storage_key = $this->get_item_key( $key ); 
  385.  
  386. $sub_path = $this->_get_path( $storage_key ); 
  387. $path = $this->_cache_dir . DIRECTORY_SEPARATOR . 
  388. ( $group ? $group . DIRECTORY_SEPARATOR : '' ) . $sub_path; 
  389.  
  390. $dir = dirname( $path ); 
  391.  
  392. if ( !@is_dir( $dir ) ) { 
  393. if ( !Util_File::mkdir_from( $dir, W3TC_CACHE_DIR ) ) 
  394. return false; 
  395.  
  396. $fp = @fopen( $path, $mode ); 
  397. return $fp;