Source for file FileStorage.php
Documentation is available at FileStorage.php
6: * Copyright (c) 2004, 2009 David Grudl (http://davidgrudl.com)
8: * This source file is subject to the "Nette license" that is bundled
9: * with this package in the file license.txt.
11: * For more information please see http://nettephp.com
13: * @copyright Copyright (c) 2004, 2009 David Grudl
14: * @license http://nettephp.com/license Nette license
15: * @link http://nettephp.com
17: * @package Nette\Caching
22: require_once dirname(__FILE__) .
'/../Object.php';
24: require_once dirname(__FILE__) .
'/../Caching/ICacheStorage.php';
29: * Cache file storage.
31: * @author David Grudl
32: * @copyright Copyright (c) 2004, 2009 David Grudl
33: * @package Nette\Caching
38: * Atomic thread safe logic:
40: * 1) reading: open(r+b), lock(SH), read
41: * - delete?: delete*, close
42: * 2) deleting: open(r+b), delete*, close
43: * 3) writing: open(r+b || wb), lock(EX), truncate*, write data, write meta, close
45: * delete* = lock(EX), try unlink, if fails (on NTFS) { truncate, close, unlink } else close (on ext3)
48: /**#@+ @ignore internal cache file structure */
49: const META_HEADER_LEN =
28; // 22b signature + 6b meta-struct size + serialized meta-struct + data
50: // meta structure: array of
51: const META_TIME =
'time'; // timestamp
52: const META_SERIALIZED =
'serialized'; // is content serialized?
53: const META_PRIORITY =
'priority'; // priority
54: const META_EXPIRE =
'expire'; // expiration timestamp
55: const META_DELTA =
'delta'; // relative (sliding) expiration
56: const META_ITEMS =
'di'; // array of dependent items (file => timestamp)
57: const META_TAGS =
'tags'; // array of tags (tag => [foo])
58: const META_CALLBACKS =
'callbacks'; // array of callbacks (function, args)
61: /**#@+ additional cache structure */
67: /** @var float probability that the clean() routine is started */
68: public static $gcProbability =
0.001;
71: public static $useDirectories;
83: if (self::$useDirectories ===
NULL) {
84: self::$useDirectories =
!ini_get('safe_mode');
86: // checks whether directory is writable
89: if (!@mkdir("$dir/$uniq", 0777)) { // intentionally @
93: // tests subdirectory mode
94: if (!self::$useDirectories &&
@file_put_contents("$dir/$uniq/_", '') !==
FALSE) { // intentionally @
95: self::$useDirectories =
TRUE;
102: $this->useDirs = (bool)
self::$useDirectories;
114: * @return mixed|NULL
119: if ($meta &&
$this->verify($meta)) {
130: * Verifies dependencies.
134: private function verify($meta)
137: if (!empty($meta[self::META_DELTA])) {
138: // meta[file] was added by readMeta()
142: } elseif (!empty($meta[self::META_EXPIRE]) &&
$meta[self::META_EXPIRE] <
time()) {
150: if (!empty($meta[self::META_ITEMS])) {
151: foreach ($meta[self::META_ITEMS] as $depFile =>
$time) {
153: if ($m[self::META_TIME] !==
$time) break 2;
154: if ($m &&
!$this->verify($m)) break 2;
161: $this->delete($meta[self::HANDLE], $meta[self::FILE]); // meta[handle] & meta[file] was added by readMeta()
168: * Writes item into the cache.
171: * @param array dependencies
172: * @return bool TRUE if no problem
174: public function write($key, $data, array $dp)
177: self::META_TIME =>
microtime(),
180: if (!is_string($data)) {
181: $data =
serialize($data);
182: $meta[self::META_SERIALIZED] =
TRUE;
186: $meta[self::META_PRIORITY] = (int)
$dp[Cache::PRIORITY];
191: $meta[self::META_EXPIRE] =
$dp[Cache::EXPIRE] +
time(); // absolute time
193: $meta[self::META_DELTA] = (int)
$dp[Cache::EXPIRE]; // sliding time
202: foreach ((array)
$dp[Cache::ITEMS] as $item) {
205: $meta[self::META_ITEMS][$depFile] =
$m[self::META_TIME];
211: $meta[self::META_CALLBACKS] =
$dp[Cache::CALLBACKS];
221: $handle =
@fopen($cacheFile, 'r+b'); // intentionally @
223: $handle =
fopen($cacheFile, 'wb'); // intentionally @
233: $head =
'<?php //netteCache[01]' .
str_pad((string)
strlen($head), 6, '0', STR_PAD_LEFT) .
$head;
238: if (fwrite($handle, $data, $dataLen) ===
$dataLen) {
240: if (fwrite($handle, $head, $headLen) ===
$headLen) {
247: $this->delete($handle, $cacheFile);
254: * Removes item from the cache.
256: * @return bool TRUE if no problem
263: $this->delete($meta[self::HANDLE], $cacheFile);
271: * Removes items from the cache by conditions & garbage collector.
272: * @param array conditions
273: * @return bool TRUE if no problem
277: $tags =
isset($conds[Cache::TAGS]) ?
array_flip((array)
$conds[Cache::TAGS]) :
array();
279: $priority =
isset($conds[Cache::PRIORITY]) ?
$conds[Cache::PRIORITY] : -
1;
281: $all =
!empty($conds[Cache::ALL]);
285: $base =
$this->dir .
DIRECTORY_SEPARATOR .
'c';
286: $iterator =
new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->dir), RecursiveIteratorIterator::CHILD_FIRST);
287: foreach ($iterator as $entry) {
291: if ($entry->isDir()) {
292: @rmdir((string)
$entry); // intentionally @
297: if (!$meta) continue 2;
300: if (!empty($meta[self::META_EXPIRE]) &&
$meta[self::META_EXPIRE] <
$now) {
304: if (!empty($meta[self::META_PRIORITY]) &&
$meta[self::META_PRIORITY] <=
$priority) {
316: $this->delete($meta[self::HANDLE], (string)
$entry);
325: * Reads cache data from disk.
326: * @param string file path
327: * @param int lock mode
328: * @return array|NULL
332: $handle =
@fopen($file, 'r+b'); // intentionally @
333: if (!$handle) return NULL;
338: if ($head &&
strlen($head) ===
self::META_HEADER_LEN) {
343: fseek($handle, $size +
self::META_HEADER_LEN); // needed by PHP < 5.2.6
344: $meta[self::FILE] =
$file;
345: $meta[self::HANDLE] =
$handle;
357: * Reads cache data from disk and closes cache file handle.
366: if (empty($meta[self::META_SERIALIZED])) {
376: * Returns file name.
382: if ($this->useDirs) {
393: * Deletes and closes file.
398: private static function delete($handle, $file)
406: @unlink($file); // intentionally @; not atomic