PageRenderTime 48ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/Cache/src/storage/file.php

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