PageRenderTime 61ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/ezcomponents/Cache/src/storage/file.php

http://hppg.googlecode.com/
PHP | 927 lines | 458 code | 40 blank | 429 comment | 58 complexity | a746e269b7b02b15cf5fbb27973561bd MD5 | raw file
Possible License(s): GPL-3.0, BSD-3-Clause
  1. <?php
  2. /**
  3. * File containing the ezcCacheStorageFile class.
  4. *
  5. * @package Cache
  6. * @version 1.5
  7. * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved.
  8. * @license http://ez.no/licenses/new_bsd New BSD License
  9. * @filesource
  10. */
  11. /**
  12. * This class implements most of the methods which have been declared abstract
  13. * in {@link ezcCacheStorage}, but also declares 2 new methods abstract, which
  14. * have to be implemented by storage driver itself.
  15. *
  16. * This class is a common base class for all file system based storage classes.
  17. * To implement a file system based cache storage, you simply have to derive
  18. * from this class and implement the {@link ezcCacheStorageFile::fetchData()}
  19. * and {@link ezcCacheStorageFile::prepareData()} methods. Everything else is
  20. * done for you by the ezcCacheStorageFile base class.
  21. *
  22. * For example code of using a cache storage, see {@link ezcCacheManager}.
  23. *
  24. * The Cache package already contains several implementations of
  25. * {@link ezcCacheStorageFile}. As there are:
  26. *
  27. * - ezcCacheStorageFileArray
  28. * - ezcCacheStorageFileEvalArray
  29. * - ezcCacheStorageFilePlain
  30. *
  31. * @package Cache
  32. * @version 1.5
  33. */
  34. abstract class ezcCacheStorageFile extends ezcCacheStorage implements ezcCacheStackableStorage, ezcCacheStackMetaDataStorage
  35. {
  36. /**
  37. * Resource used for the lock file.
  38. *
  39. * @var resource(file)
  40. */
  41. protected $lockResource = false;
  42. /**
  43. * Creates a new cache storage in the given location.
  44. * Creates a new cache storage for a given location. The location in case
  45. * of this storage class is a valid file system directory.
  46. *
  47. * Options can contain the 'ttl' ( Time-To-Life ). This is per default set
  48. * to 1 day. The option 'permissions' can be used to define the file
  49. * permissions of created cache items. Specific ezcCacheStorageFile
  50. * implementations can have additional options.
  51. *
  52. * For details about the options see {@link ezcCacheStorageFileOptions}.
  53. *
  54. * @param string $location Path to the cache location
  55. * @param array(string=>string) $options Options for the cache.
  56. *
  57. * @throws ezcBaseFileNotFoundException
  58. * If the storage location does not exist. This should usually not
  59. * happen, since {@link ezcCacheManager::createCache()} already
  60. * performs sanity checks for the cache location. In case this
  61. * exception is thrown, your cache location has been corrupted
  62. * after the cache was configured.
  63. * @throws ezcBaseFileNotFoundException
  64. * If the storage location is not a directory. This should usually
  65. * not happen, since {@link ezcCacheManager::createCache()} already
  66. * performs sanity checks for the cache location. In case this
  67. * exception is thrown, your cache location has been corrupted
  68. * after the cache was configured.
  69. * @throws ezcBaseFilePermissionException
  70. * If the storage location is not writeable. This should usually not
  71. * happen, since {@link ezcCacheManager::createCache()} already
  72. * performs sanity checks for the cache location. In case this
  73. * exception is thrown, your cache location has been corrupted
  74. * after the cache was configured.
  75. * @throws ezcBasePropertyNotFoundException
  76. * If you tried to set a non-existent option value. The accepted
  77. * options depend on the ezcCacheStorage implementation and may
  78. * vary.
  79. */
  80. public function __construct( $location, $options = array() )
  81. {
  82. // Sanity check location
  83. if ( !file_exists( $location ) || !is_dir( $location ) )
  84. {
  85. throw new ezcBaseFileNotFoundException(
  86. $location,
  87. 'cache location',
  88. 'Does not exist or is no directory.'
  89. );
  90. }
  91. if ( !is_readable( $location ) )
  92. {
  93. throw new ezcBaseFilePermissionException(
  94. $location,
  95. ezcBaseFileException::READ,
  96. 'Cache location is not readable.'
  97. );
  98. }
  99. if ( !is_writeable( $location ) )
  100. {
  101. throw new ezcBaseFilePermissionException(
  102. $location,
  103. ezcBaseFileException::WRITE,
  104. 'Cache location is not writeable.'
  105. );
  106. }
  107. parent::__construct( $location );
  108. // Overwrite parent set options with new ezcCacheFileStorageOptions
  109. $this->properties['options'] = new ezcCacheStorageFileOptions( $options );
  110. }
  111. /**
  112. * Fetch data from the cache.
  113. * This method does the fetching of the data itself. In this case, the
  114. * method simply includes the file and returns the value returned by the
  115. * include ( or false on failure ).
  116. *
  117. * @param string $filename The file to fetch data from.
  118. * @return mixed The fetched data or false on failure.
  119. */
  120. abstract protected function fetchData( $filename );
  121. /**
  122. * Serialize the data for storing.
  123. * Serializes a PHP variable ( except type resource and object ) to a
  124. * executable PHP code representation string.
  125. *
  126. * @param mixed $data Simple type or array
  127. * @return string The serialized data
  128. *
  129. * @throws ezcCacheInvalidDataException
  130. * If the data submitted can not be handled by the implementation
  131. * of {@link ezcCacheStorageFile}. Most implementations can not
  132. * handle objects and resources.
  133. */
  134. abstract protected function prepareData( $data );
  135. /**
  136. * Store data to the cache storage.
  137. * This method stores the given cache data into the cache, assigning the
  138. * ID given to it.
  139. *
  140. * The type of cache data which is expected by a ezcCacheStorage depends on
  141. * its implementation. In most cases strings and arrays will be accepted,
  142. * in some rare cases only strings might be accepted.
  143. *
  144. * Using attributes you can describe your cache data further. This allows
  145. * you to deal with multiple cache data at once later. Some ezcCacheStorage
  146. * implementations also use the attributes for storage purposes. Attributes
  147. * form some kind of "extended ID".
  148. *
  149. * @param string $id Unique identifier for the data.
  150. * @param mixed $data The data to store.
  151. * @param array(string=>string) $attributes Attributes describing the
  152. * cached data.
  153. *
  154. * @return string The ID string of the newly cached data.
  155. *
  156. * @throws ezcBaseFilePermissionException
  157. * If an already existsing cache file could not be unlinked to
  158. * store the new data (may occur, when a cache item's TTL
  159. * has expired and the file should be stored with more actual
  160. * data). This exception means most likely that your cache directory
  161. * has been corrupted by external influences (file permission
  162. * change).
  163. * @throws ezcBaseFilePermissionException
  164. * If the directory to store the cache file could not be created.
  165. * This exception means most likely that your cache directory
  166. * has been corrupted by external influences (file permission
  167. * change).
  168. * @throws ezcBaseFileIoException
  169. * If an error occured while writing the data to the cache. If this
  170. * exception occurs, a serious error occured and your storage might
  171. * be corruped (e.g. broken network connection, file system broken,
  172. * ...).
  173. * @throws ezcCacheInvalidDataException
  174. * If the data submitted can not be handled by the implementation
  175. * of {@link ezcCacheStorageFile}. Most implementations can not
  176. * handle objects and resources.
  177. */
  178. public function store( $id, $data, $attributes = array() )
  179. {
  180. $filename = $this->properties['location']
  181. . $this->generateIdentifier( $id, $attributes );
  182. if ( file_exists( $filename ) )
  183. {
  184. if ( unlink( $filename ) === false )
  185. {
  186. throw new ezcBaseFilePermissionException(
  187. $filename,
  188. ezcBaseFileException::WRITE,
  189. 'Could not delete existsing cache file.'
  190. );
  191. }
  192. }
  193. $dataStr = $this->prepareData( $data );
  194. $dirname = dirname( $filename );
  195. if ( !is_dir( $dirname ) && !mkdir( $dirname, 0777, true ) )
  196. {
  197. throw new ezcBaseFilePermissionException(
  198. $dirname,
  199. ezcBaseFileException::WRITE,
  200. 'Could not create directory to stor cache file.'
  201. );
  202. }
  203. $this->storeRawData( $filename, $dataStr );
  204. if ( ezcBaseFeatures::os() !== "Windows" )
  205. {
  206. chmod( $filename, $this->options->permissions );
  207. }
  208. return $id;
  209. }
  210. /**
  211. * Actually stores the given data.
  212. *
  213. * @param string $filename
  214. * @param string $data
  215. * @return void
  216. *
  217. * @throws ezcBaseFileIoException
  218. * if the store fails.
  219. */
  220. protected function storeRawData( $filename, $data )
  221. {
  222. if ( file_put_contents( $filename, $data ) !== strlen( $data ) )
  223. {
  224. throw new ezcBaseFileIoException(
  225. $filename,
  226. ezcBaseFileException::WRITE,
  227. 'Could not write data to cache file.'
  228. );
  229. }
  230. }
  231. /**
  232. * Restore data from the cache.
  233. * Restores the data associated with the given cache and
  234. * returns it. Please see {@link ezcCacheStorage::store()}
  235. * for more detailed information of cachable datatypes.
  236. *
  237. * During access to cached data the caches are automatically
  238. * expired. This means, that the ezcCacheStorage object checks
  239. * before returning the data if it's still actual. If the cache
  240. * has expired, data will be deleted and false is returned.
  241. *
  242. * You should always provide the attributes you assigned, although
  243. * the cache storages must be able to find a cache ID even without
  244. * them. BEWARE: Finding cache data only by ID can be much
  245. * slower than finding it by ID and attributes.
  246. *
  247. * Note that with the {@link ezcCacheStorageFilePlain} all restored data
  248. * will be of type string. If you expect a different data type you need to
  249. * perform a cast after restoring.
  250. *
  251. * @param string $id The item ID to restore.
  252. * @param array(string=>string) $attributes Attributes describing the
  253. * data to restore.
  254. * @param bool $search Whether to search for items
  255. * if not found directly.
  256. *
  257. * @return mixed|bool The cached data on success, otherwise false.
  258. *
  259. * @throws ezcBaseFilePermissionException
  260. * If an already existsing cache file could not be unlinked.
  261. * This exception means most likely that your cache directory
  262. * has been corrupted by external influences (file permission
  263. * change).
  264. */
  265. public function restore( $id, $attributes = array(), $search = false )
  266. {
  267. $filename = $this->properties['location']
  268. . $this->generateIdentifier( $id, $attributes );
  269. if ( file_exists( $filename ) === false )
  270. {
  271. if ( $search === true
  272. && count( $files = $this->search( $id, $attributes ) ) === 1 )
  273. {
  274. $filename = $files[0];
  275. }
  276. else
  277. {
  278. return false;
  279. }
  280. }
  281. // No cached data
  282. if ( file_exists( $filename ) === false )
  283. {
  284. return false;
  285. }
  286. // Cached data outdated, purge it.
  287. if ( $this->calcLifetime( $filename ) == 0
  288. && $this->properties['options']['ttl'] !== false )
  289. {
  290. $this->delete( $id, $attributes );
  291. return false;
  292. }
  293. return ( $this->fetchData( $filename ) );
  294. }
  295. /**
  296. * Delete data from the cache.
  297. * Purges the cached data for a given ID and or attributes. Using an ID
  298. * purges only the cache data for just this ID.
  299. *
  300. * Additional attributes provided will matched additionally. This can give
  301. * you an immense speed improvement against just searching for ID ( see
  302. * {@link ezcCacheStorage::restore()} ).
  303. *
  304. * If you only provide attributes for deletion of cache data, all cache
  305. * data matching these attributes will be purged.
  306. *
  307. * @param string $id The item ID to purge.
  308. * @param array(string=>string) $attributes Attributes describing the
  309. * data to restore.
  310. * @param bool $search Whether to search for items
  311. * if not found directly.
  312. * @return void
  313. *
  314. * @throws ezcBaseFilePermissionException
  315. * If an already existsing cache file could not be unlinked.
  316. * This exception means most likely that your cache directory
  317. * has been corrupted by external influences (file permission
  318. * change).
  319. */
  320. public function delete( $id = null, $attributes = array(), $search = false )
  321. {
  322. $filename = $this->properties['location']
  323. . $this->generateIdentifier( $id, $attributes );
  324. $filesToDelete = array();
  325. if ( file_exists( $filename ) )
  326. {
  327. $filesToDelete[] = $filename;
  328. }
  329. else if ( $search === true )
  330. {
  331. $filesToDelete = $this->search( $id, $attributes );
  332. }
  333. $deletedIds = array();
  334. foreach ( $filesToDelete as $filename )
  335. {
  336. if ( unlink( $filename ) === false )
  337. {
  338. throw new ezcBaseFilePermissionException(
  339. $filename,
  340. ezcBaseFileException::WRITE,
  341. 'Could not unlink cache file.'
  342. );
  343. }
  344. $deleted = $this->extractIdentifier( $filename );
  345. $deletedIds[] = $deleted['id'];
  346. }
  347. return $deletedIds;
  348. }
  349. /**
  350. * Return the number of items in the cache matching a certain criteria.
  351. * This method determines if cache data described by the given ID and/or
  352. * attributes exists. It returns the number of cache data items found.
  353. *
  354. * @param string $id The item ID.
  355. * @param array(string=>string) $attributes Attributes describing the
  356. * data to restore.
  357. * @return int Number of data items matching the criteria.
  358. */
  359. public function countDataItems( $id = null, $attributes = array() )
  360. {
  361. return count( $this->search( $id, $attributes ) );
  362. }
  363. /**
  364. * Returns the time ( in seconds ) which remains for a cache object,
  365. * before it gets outdated. In case the cache object is already
  366. * outdated or does not exist, this method returns 0.
  367. *
  368. * @param string $id The item ID.
  369. * @param array(string=>string) $attributes Attributes describing the
  370. * data to restore.
  371. * @access public
  372. * @return int The remaining lifetime (0 if nonexists or oudated).
  373. */
  374. public function getRemainingLifetime( $id, $attributes = array() )
  375. {
  376. if ( count( $objects = $this->search( $id, $attributes ) ) > 0 )
  377. {
  378. return $this->calcLifetime( $objects[0] );
  379. }
  380. return 0;
  381. }
  382. /**
  383. * Purges the given number of cache items.
  384. *
  385. * This method minimally purges the $limit number of outdated cache items
  386. * from the storage. If limit is left out, all outdated items are purged.
  387. * The purged item IDs are returned.
  388. *
  389. * @param int $limit
  390. * @return array(string)
  391. */
  392. public function purge( $limit = null )
  393. {
  394. $purgeCount = 0;
  395. return $this->purgeRecursive( $this->properties['location'], $limit, $purgeCount );
  396. }
  397. /**
  398. * Recursively purge cache items.
  399. *
  400. * Recursively purges $dir until $limit is reached. $purgeCount is the
  401. * number of already purged items.
  402. *
  403. * @param string $dir
  404. * @param int $limit
  405. * @param int $purgeCount
  406. */
  407. private function purgeRecursive( $dir, $limit, &$purgeCount )
  408. {
  409. $purgedIds = array();
  410. // Deal with files in the directory
  411. if ( ( $files = glob( "{$dir}*{$this->properties['options']->extension}" ) ) === false )
  412. {
  413. throw new ezcBaseFileNotFoundException(
  414. $dir,
  415. 'cache location',
  416. 'Produced an error while globbing for files.'
  417. );
  418. }
  419. foreach ( $files as $file )
  420. {
  421. if ( $this->calcLifetime( $file ) == 0 )
  422. {
  423. if ( @unlink( $file ) === false )
  424. {
  425. throw new ezcBaseFilePermissionException(
  426. $file,
  427. ezcBaseFileException::WRITE,
  428. 'Could not unlink cache file.'
  429. );
  430. }
  431. $fileInfo = $this->extractIdentifier( $file );
  432. $purgedIds[] = $fileInfo['id'];
  433. ++$purgeCount;
  434. }
  435. // Stop purging if limit is reached
  436. if ( $limit !== null && $purgeCount >= $limit )
  437. {
  438. return $purgedIds;
  439. }
  440. }
  441. // Deal with sub dirs, this function expects them to be marked with a
  442. // slash because of the property $location
  443. if ( ( $dirs = glob( "$dir*", GLOB_ONLYDIR | GLOB_MARK ) ) === false )
  444. {
  445. throw new ezcBaseFileNotFoundException(
  446. $dir,
  447. 'cache location',
  448. 'Produced an error while globbing for directories.'
  449. );
  450. }
  451. foreach ( $dirs as $dir )
  452. {
  453. $purgedIds = array_merge(
  454. $purgedIds,
  455. $this->purgeRecursive( $dir, $limit, $purgeCount )
  456. );
  457. // Stop purging if limit is reached
  458. if ( $limit !== null && $purgeCount >= $limit )
  459. {
  460. return $purgedIds;
  461. }
  462. }
  463. // Finished purging, return IDs.
  464. return $purgedIds;
  465. }
  466. /**
  467. * Resets the whole storage.
  468. *
  469. * Deletes all data in the storage including {@link ezcCacheStackMetaData}
  470. * that was stored using {@link storeMetaData()}.
  471. */
  472. public function reset()
  473. {
  474. $files = glob( "{$this->properties['location']}*" );
  475. foreach ( $files as $file )
  476. {
  477. if ( is_dir( $file ) )
  478. {
  479. ezcBaseFile::removeRecursive( $file );
  480. }
  481. else
  482. {
  483. if ( @unlink( $file ) === false )
  484. {
  485. throw new ezcBaseFilePermissionException(
  486. $file,
  487. ezcBaseFileException::REMOVE,
  488. 'Could not unlink cache file.'
  489. );
  490. }
  491. }
  492. }
  493. }
  494. /**
  495. * Search the storage for data.
  496. *
  497. * @param string $id An item ID.
  498. * @param array(string=>string) $attributes Attributes describing the
  499. * data to restore.
  500. * @return array(int=>string) Found cache items.
  501. */
  502. protected function search( $id = null, $attributes = array() )
  503. {
  504. $globArr = explode( "-", $this->generateIdentifier( $id, $attributes ), 2 );
  505. if ( sizeof( $globArr ) > 1 )
  506. {
  507. $glob = $globArr[0] . "-" . strtr( $globArr[1], array( '-' => '*', '.' => '*' ) );
  508. }
  509. else
  510. {
  511. $glob = strtr( $globArr[0], array( '-' => '*', '.' => '*' ) );
  512. }
  513. $glob = ( $id === null ? '*' : '' ) . $glob;
  514. return $this->searchRecursive( $glob, $this->properties['location'] );
  515. }
  516. /**
  517. * Search the storage for data recursively.
  518. *
  519. * @param string $pattern Pattern used with {@link glob()}.
  520. * @param mixed $directory Directory to search in.
  521. * @return array(int=>string) Found cache items.
  522. */
  523. protected function searchRecursive( $pattern, $directory )
  524. {
  525. $itemArr = glob( $directory . $pattern );
  526. $dirArr = glob( $directory . "*", GLOB_ONLYDIR );
  527. foreach ( $dirArr as $dirEntry )
  528. {
  529. $result = $this->searchRecursive( $pattern, "$dirEntry/" );
  530. $itemArr = array_merge( $itemArr, $result );
  531. }
  532. return $itemArr;
  533. }
  534. /**
  535. * Checks the path in the location property exists, and is read-/writable. It
  536. * throws an exception if not.
  537. *
  538. * @throws ezcBaseFileNotFoundException
  539. * If the storage location does not exist. This should usually not
  540. * happen, since {@link ezcCacheManager::createCache()} already
  541. * performs sanity checks for the cache location. In case this
  542. * exception is thrown, your cache location has been corrupted
  543. * after the cache was configured.
  544. * @throws ezcBaseFileNotFoundException
  545. * If the storage location is not a directory. This should usually
  546. * not happen, since {@link ezcCacheManager::createCache()} already
  547. * performs sanity checks for the cache location. In case this
  548. * exception is thrown, your cache location has been corrupted
  549. * after the cache was configured.
  550. * @throws ezcBaseFilePermissionException
  551. * If the storage location is not writeable. This should usually not
  552. * happen, since {@link ezcCacheManager::createCache()} already
  553. * performs sanity checks for the cache location. In case this
  554. * exception is thrown, your cache location has been corrupted
  555. * after the cache was configured.
  556. */
  557. protected function validateLocation()
  558. {
  559. if ( file_exists( $this->properties['location'] ) === false )
  560. {
  561. throw new ezcBaseFileNotFoundException(
  562. $this->properties['location'],
  563. 'cache location'
  564. );
  565. }
  566. if ( is_dir( $this->properties['location'] ) === false )
  567. {
  568. throw new ezcBaseFileNotFoundException(
  569. $this->properties['location'],
  570. 'cache location',
  571. 'Cache location not a directory.'
  572. );
  573. }
  574. if ( is_writeable( $this->properties['location'] ) === false )
  575. {
  576. throw new ezcBaseFilePermissionException(
  577. $this->properties['location'],
  578. ezcBaseFileException::WRITE,
  579. 'Cache location is not a directory.'
  580. );
  581. }
  582. }
  583. /**
  584. * Generate the storage internal identifier from ID and attributes.
  585. *
  586. * Generates the storage internal identifier out of the provided ID and the
  587. * attributes. This is the default implementation and can be overloaded if
  588. * necessary.
  589. *
  590. * @param string $id The ID.
  591. * @param array(string=>string) $attributes Attributes describing the
  592. * data to restore.
  593. * @return string The generated identifier
  594. */
  595. public function generateIdentifier( $id, $attributes = null )
  596. {
  597. $filename = (string) $id;
  598. $illegalFileNameChars = array(
  599. ' ' => '_',
  600. '/' => DIRECTORY_SEPARATOR,
  601. '\\' => DIRECTORY_SEPARATOR,
  602. );
  603. $filename = strtr( $filename, $illegalFileNameChars );
  604. // Chars used for filename concatination
  605. $illegalChars = array(
  606. '-' => '#',
  607. ' ' => '%',
  608. '=' => '+',
  609. '.' => '+',
  610. );
  611. if ( is_array( $attributes ) && count( $attributes ) > 0 )
  612. {
  613. ksort( $attributes );
  614. foreach ( $attributes as $key => $val )
  615. {
  616. $attrStr = '-' . strtr( $key, $illegalChars )
  617. . '=' . strtr( $val, $illegalChars );
  618. if ( strlen( $filename . $attrStr ) > 250 )
  619. {
  620. // Max filename length
  621. break;
  622. }
  623. $filename .= $attrStr;
  624. }
  625. }
  626. else
  627. {
  628. $filename .= '-';
  629. }
  630. return $filename . $this->properties['options']['extension'];
  631. }
  632. /**
  633. * Restores and returns the meta data struct.
  634. *
  635. * This method fetches the meta data stored in the storage and returns the
  636. * according struct of type {@link ezcCacheStackMetaData}. The meta data
  637. * must be stored inside the storage, but should not be visible as normal
  638. * cache items to the user.
  639. *
  640. * @return ezcCacheStackMetaData|null
  641. */
  642. public function restoreMetaData()
  643. {
  644. // Silence require warnings. It's ok that meta data does not exist.
  645. $dataArr = @$this->fetchData(
  646. $this->properties['location'] . $this->properties['options']->metaDataFile
  647. );
  648. $result = null;
  649. if ( $dataArr !== false )
  650. {
  651. $result = new $dataArr['class']();
  652. $result->setState( $dataArr['data'] );
  653. }
  654. return $result;
  655. }
  656. /**
  657. * Stores the given meta data struct.
  658. *
  659. * This method stores the given $metaData inside the storage. The data must
  660. * be stored with the same mechanism that the storage itself uses. However,
  661. * it should not be stored as a normal cache item, if possible, to avoid
  662. * accedental user manipulation.
  663. *
  664. * @param ezcCacheStackMetaData $metaData
  665. * @return void
  666. */
  667. public function storeMetaData( ezcCacheStackMetaData $metaData )
  668. {
  669. $dataArr = array(
  670. 'class' => get_class( $metaData ),
  671. 'data' => $metaData->getState(),
  672. );
  673. $this->storeRawData(
  674. $this->properties['location'] . $this->properties['options']->metaDataFile,
  675. $this->prepareData( $dataArr )
  676. );
  677. }
  678. /**
  679. * Acquire a lock on the storage.
  680. *
  681. * This method acquires a lock on the storage. If locked, the storage must
  682. * block all other method calls until the lock is freed again using {@link
  683. * ezcCacheStackMetaDataStorage::unlock()}. Methods that are called within
  684. * the request that successfully acquired the lock must succeed as usual.
  685. *
  686. * @return void
  687. */
  688. public function lock()
  689. {
  690. $lockFile = $this->properties['location'] . $this->properties['options']->lockFile;
  691. while ( $this->lockResource === false )
  692. {
  693. clearstatcache();
  694. $this->lockResource = @fopen( $lockFile, 'x' );
  695. // Wait for lock to get freed
  696. if ( $this->lockResource === false )
  697. {
  698. usleep( $this->properties['options']->lockWaitTime );
  699. }
  700. // Check if lock is to be considered dead. Might result in a
  701. // nonrelevant race condition if the lock file disappears between
  702. // fs calls. To avoid warnings in this case, the calls are
  703. // silenced.
  704. if ( file_exists( $lockFile ) && ( time() - @filemtime( $lockFile ) ) > $this->properties['options']->maxLockTime )
  705. {
  706. @unlink( $lockFile );
  707. }
  708. }
  709. }
  710. /**
  711. * Release a lock on the storage.
  712. *
  713. * This method releases the lock of the storage, that has been acquired via
  714. * {@link ezcCacheStackMetaDataStorage::lock()}. After this method has been
  715. * called, blocked method calls (including calls to lock()) can suceed
  716. * again.
  717. *
  718. * @return void
  719. */
  720. public function unlock()
  721. {
  722. // If the resource is already removed, nothing to do
  723. if ( $this->lockResource !== false )
  724. {
  725. fclose( $this->lockResource );
  726. @unlink(
  727. $this->properties['location'] . $this->properties['options']->lockFile
  728. );
  729. $this->lockResource = false;
  730. }
  731. }
  732. /**
  733. * Set new options.
  734. * This method allows you to change the options of a cache file storage. Change
  735. * of options take effect directly after this method has been called. The
  736. * available options depend on the ezcCacheStorageFile implementation. All
  737. * implementations have to offer the following options:
  738. *
  739. * - ttl The time-to-life. After this time span, a cache item becomes
  740. * invalid and will be purged. The
  741. * {@link ezcCacheStorage::restore()} method will then return
  742. * false.
  743. * - extension The "extension" for your cache items. This is usually the
  744. * file name extension, when you deal with file system based
  745. * caches or e.g. a database ID extension.
  746. * - permissions The file permissions to set for new files.
  747. *
  748. * The usage of ezcCacheStorageOptions and arrays for setting options is
  749. * deprecated, but still supported. You should migrate to
  750. * ezcCacheStorageFileOptions.
  751. *
  752. * @param ezcCacheStorageFileOptions $options The options to set (accepts
  753. * ezcCacheStorageOptions or
  754. * array for compatibility
  755. * reasons, too).
  756. *
  757. * @throws ezcBasePropertyNotFoundException
  758. * If you tried to set a non-existent option value. The accepted
  759. * options depend on the ezcCacheStorage implementation and may
  760. * vary.
  761. * @throws ezcBaseValueException
  762. * If the value is not valid for the desired option.
  763. * @throws ezcBaseValueException
  764. * If you submit neither an instance of ezcCacheStorageFileOptions,
  765. * nor an instance of ezcCacheStorageOptions nor an array.
  766. */
  767. public function setOptions( $options )
  768. {
  769. if ( is_array( $options ) )
  770. {
  771. $this->properties['options']->merge( $options );
  772. }
  773. else if ( $options instanceof ezcCacheStorageFileOptions )
  774. {
  775. $this->properties['options'] = $options;
  776. }
  777. else if ( $options instanceof ezcCacheStorageOptions )
  778. {
  779. $this->properties['options']->mergeStorageOptions( $options );
  780. }
  781. else
  782. {
  783. throw new ezcBaseValueException(
  784. 'options',
  785. $options,
  786. 'instance of ezcCacheStorageFileOptions or (deprecated) ezcCacheStorageOptions'
  787. );
  788. }
  789. }
  790. /**
  791. * Property write access.
  792. *
  793. * @param string $propertyName Name of the property.
  794. * @param mixed $val The value for the property.
  795. *
  796. * @throws ezcBaseValueException
  797. * If the value for the property options is not an instance of
  798. * ezcCacheStorageOptions.
  799. * @ignore
  800. */
  801. public function __set( $propertyName, $val )
  802. {
  803. switch ( $propertyName )
  804. {
  805. case 'options':
  806. if ( $val instanceof ezcCacheStorageFileOptions )
  807. {
  808. $this->properties['options'] = $val;
  809. return;
  810. }
  811. if ( $val instanceof ezcCacheStorageOptions )
  812. {
  813. $this->properties['options']->mergeStorageOptions( $val );
  814. return;
  815. }
  816. throw new ezcBaseValueException(
  817. $propertyName,
  818. $val,
  819. 'instance of ezcCacheStorageFileOptions'
  820. );
  821. }
  822. throw new ezcBasePropertyNotFoundException( $propertyName );
  823. }
  824. /**
  825. * Calculates the lifetime remaining for a cache object.
  826. *
  827. * This calculates the time a cached object stays valid and returns it. In
  828. * case the TTL is set to false, this method always returns a value of 1.
  829. *
  830. * @param string $file The file to calculate the remaining lifetime for.
  831. * @return int The remaining lifetime in seconds (0 if no time remaining).
  832. */
  833. protected function calcLifetime( $file )
  834. {
  835. $ttl = $this->options->ttl;
  836. if ( file_exists( $file ) && ( $modTime = filemtime( $file ) ) !== false )
  837. {
  838. if ( $ttl === false )
  839. {
  840. return 1;
  841. }
  842. return (
  843. ( $lifeTime = time() - $modTime ) < $ttl
  844. ? $ttl - $lifeTime
  845. : 0
  846. );
  847. }
  848. return 0;
  849. }
  850. /**
  851. * Extracts ID, attributes and the file extension from a filename.
  852. *
  853. * @param string $filename
  854. * @return array('id'=>string,'attributes'=>string,'ext'=>string)
  855. */
  856. private function extractIdentifier( $filename )
  857. {
  858. // Regex to split up the file name into id, attributes and extension
  859. $regex = '(
  860. (?:' . preg_quote( $this->properties['location'] ) . ')
  861. (?P<id>.*)
  862. (?P<attr>(?:-[^-=]+=[^-]+)*)
  863. -? # This is added if no attributes are supplied. For whatever reason...
  864. (?P<ext>' . preg_quote( $this->options->extension ) . ')
  865. )Ux';
  866. if ( preg_match( $regex, $filename, $matches ) !== 1 )
  867. {
  868. // @TODO: Should this be an exception?
  869. return array(
  870. 'id' => '',
  871. 'attributes' => '',
  872. 'extension' => $this->options->extension,
  873. );
  874. }
  875. else
  876. {
  877. // Successfully split
  878. return array(
  879. 'id' => $matches['id'],
  880. 'attributes' => $matches['attr'],
  881. 'extension' => $matches['ext'],
  882. );
  883. }
  884. }
  885. }
  886. ?>