Source for file FileStorage.php
Documentation is available at FileStorage.php
6: * @copyright Copyright (c) 2004, 2010 David Grudl
7: * @license http://nettephp.com/license Nette license
8: * @link http://nettephp.com
10: * @package Nette\Caching
16: * Cache file storage.
18: * @copyright Copyright (c) 2004, 2010 David Grudl
19: * @package Nette\Caching
24: * Atomic thread safe logic:
26: * 1) reading: open(r+b), lock(SH), read
27: * - delete?: delete*, close
28: * 2) deleting: delete*
29: * 3) writing: open(r+b || wb), lock(EX), truncate*, write data, write meta, close
31: * delete* = try unlink, if fails (on NTFS) { lock(EX), truncate, close, unlink } else close (on ext3)
34: /**#@+ @ignore internal cache file structure */
35: const META_HEADER_LEN =
28; // 22b signature + 6b meta-struct size + serialized meta-struct + data
36: // meta structure: array of
37: const META_TIME =
'time'; // timestamp
38: const META_SERIALIZED =
'serialized'; // is content serialized?
39: const META_EXPIRE =
'expire'; // expiration timestamp
40: const META_DELTA =
'delta'; // relative (sliding) expiration
41: const META_ITEMS =
'di'; // array of dependent items (file => timestamp)
42: const META_CALLBACKS =
'callbacks'; // array of callbacks (function, args)
45: /**#@+ additional cache structure */
51: /** @var float probability that the clean() routine is started */
52: public static $gcProbability =
0.001;
55: public static $useDirectories;
70: if (self::$useDirectories ===
NULL) {
71: // checks whether directory is writable
74: if (!@mkdir("$dir/$uniq", 0777)) { // intentionally @
78: // tests subdirectory mode
79: self::$useDirectories =
!ini_get('safe_mode');
80: if (!self::$useDirectories &&
@file_put_contents("$dir/$uniq/_", '') !==
FALSE) { // intentionally @
81: self::$useDirectories =
TRUE;
88: $this->useDirs = (bool)
self::$useDirectories;
100: * @return mixed|NULL
105: if ($meta &&
$this->verify($meta)) {
116: * Verifies dependencies.
120: private function verify($meta)
123: if (!empty($meta[self::META_DELTA])) {
124: // meta[file] was added by readMeta()
128: } elseif (!empty($meta[self::META_EXPIRE]) &&
$meta[self::META_EXPIRE] <
time()) {
136: if (!empty($meta[self::META_ITEMS])) {
137: foreach ($meta[self::META_ITEMS] as $depFile =>
$time) {
139: if ($m[self::META_TIME] !==
$time) break 2;
140: if ($m &&
!$this->verify($m)) break 2;
147: $this->delete($meta[self::FILE], $meta[self::HANDLE]); // meta[handle] & meta[file] was added by readMeta()
154: * Writes item into the cache.
157: * @param array dependencies
160: public function write($key, $data, array $dp)
163: self::META_TIME =>
microtime(),
168: $meta[self::META_EXPIRE] =
$dp[Cache::EXPIRE] +
time(); // absolute time
170: $meta[self::META_DELTA] = (int)
$dp[Cache::EXPIRE]; // sliding time
175: foreach ((array)
$dp[Cache::ITEMS] as $item) {
178: $meta[self::META_ITEMS][$depFile] =
$m[self::META_TIME];
184: $meta[self::META_CALLBACKS] =
$dp[Cache::CALLBACKS];
194: $handle =
@fopen($cacheFile, 'r+b'); // intentionally @
196: $handle =
fopen($cacheFile, 'wb'); // intentionally @
207: foreach ((array)
$dp[Cache::TAGS] as $tag) {
212: $query .=
"INSERT INTO cache (file, priority) VALUES ('$dbFile', '" . (int)
$dp[Cache::PRIORITY] .
"');";
214: if (!sqlite_exec($db, "BEGIN; DELETE FROM cache WHERE file = '$dbFile'; $query COMMIT;")) {
223: if ($data instanceof
Callback ||
$data instanceof
Closure) {
224: $data =
$data->__invoke();
228: $meta[self::META_SERIALIZED] =
TRUE;
232: $head =
'<?php //netteCache[01]' .
str_pad((string)
strlen($head), 6, '0', STR_PAD_LEFT) .
$head;
241: if (fwrite($handle, $data, $dataLen) !==
$dataLen) {
246: if (fwrite($handle, $head, $headLen) !==
$headLen) {
254: $this->delete($cacheFile, $handle);
260: * Removes item from the cache.
272: * Removes items from the cache by conditions & garbage collector.
273: * @param array conditions
278: $all =
!empty($conds[Cache::ALL]);
279: $collector =
empty($conds);
281: // cleaning using file iterator
282: if ($all ||
$collector) {
284: $base =
$this->dir .
DIRECTORY_SEPARATOR .
'c';
285: $iterator =
new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->dir), RecursiveIteratorIterator::CHILD_FIRST);
286: foreach ($iterator as $entry) {
287: $path = (string)
$entry;
291: if ($entry->isDir()) { // collector: remove empty dirs
296: $this->delete($path);
298: } else { // collector
300: if (!$meta) continue;
302: if (!empty($meta[self::META_EXPIRE]) &&
$meta[self::META_EXPIRE] <
$now) {
303: $this->delete($path, $meta[self::HANDLE]);
317: // cleaning using journal
320: foreach ((array)
$conds[Cache::TAGS] as $tag) {
327: $query[] =
"priority <= " . (int)
$conds[Cache::PRIORITY];
330: if (isset($query)) {
334: foreach ($files as $file) {
335: $this->delete($file);
344: * Reads cache data from disk.
345: * @param string file path
346: * @param int lock mode
347: * @return array|NULL
351: $handle =
@fopen($file, 'r+b'); // intentionally @
352: if (!$handle) return NULL;
357: if ($head &&
strlen($head) ===
self::META_HEADER_LEN) {
362: fseek($handle, $size +
self::META_HEADER_LEN); // needed by PHP < 5.2.6
363: $meta[self::FILE] =
$file;
364: $meta[self::HANDLE] =
$handle;
376: * Reads cache data from disk and closes cache file handle.
385: if (empty($meta[self::META_SERIALIZED])) {
395: * Returns file name.
401: if ($this->useDirs) {
412: * Deletes and closes file.
417: private static function delete($file, $handle =
NULL)
425: $handle =
@fopen($file, 'r+'); // intentionally @
431: @unlink($file); // intentionally @; not atomic
438: * Returns SQLite resource.
443: if ($this->db ===
NULL) {
448: @sqlite_exec($this->db, 'CREATE TABLE cache (file VARCHAR NOT NULL, priority, tag VARCHAR);
449: CREATE INDEX IDX_FILE ON cache (file); CREATE INDEX IDX_PRI ON cache (priority); CREATE INDEX IDX_TAG ON cache (tag);'); // intentionally @