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: self::$useDirectories =
!ini_get('safe_mode');
73: // checks whether directory is writable
76: if (!@mkdir("$dir/$uniq", 0777)) { // intentionally @
80: // tests subdirectory mode
81: if (!self::$useDirectories &&
@file_put_contents("$dir/$uniq/_", '') !==
FALSE) { // intentionally @
82: self::$useDirectories =
TRUE;
89: $this->useDirs = (bool)
self::$useDirectories;
101: * @return mixed|NULL
106: if ($meta &&
$this->verify($meta)) {
117: * Verifies dependencies.
121: private function verify($meta)
124: if (!empty($meta[self::META_DELTA])) {
125: // meta[file] was added by readMeta()
129: } elseif (!empty($meta[self::META_EXPIRE]) &&
$meta[self::META_EXPIRE] <
time()) {
137: if (!empty($meta[self::META_ITEMS])) {
138: foreach ($meta[self::META_ITEMS] as $depFile =>
$time) {
140: if ($m[self::META_TIME] !==
$time) break 2;
141: if ($m &&
!$this->verify($m)) break 2;
148: $this->delete($meta[self::FILE], $meta[self::HANDLE]); // meta[handle] & meta[file] was added by readMeta()
155: * Writes item into the cache.
158: * @param array dependencies
161: public function write($key, $data, array $dp)
164: self::META_TIME =>
microtime(),
167: if (!is_string($data)) {
168: $data =
serialize($data);
169: $meta[self::META_SERIALIZED] =
TRUE;
174: $meta[self::META_EXPIRE] =
$dp[Cache::EXPIRE] +
time(); // absolute time
176: $meta[self::META_DELTA] = (int)
$dp[Cache::EXPIRE]; // sliding time
181: foreach ((array)
$dp[Cache::ITEMS] as $item) {
184: $meta[self::META_ITEMS][$depFile] =
$m[self::META_TIME];
190: $meta[self::META_CALLBACKS] =
$dp[Cache::CALLBACKS];
200: $handle =
@fopen($cacheFile, 'r+b'); // intentionally @
202: $handle =
fopen($cacheFile, 'wb'); // intentionally @
213: foreach ((array)
$dp[Cache::TAGS] as $tag) {
218: $query .=
"INSERT INTO cache (file, priority) VALUES ('$dbFile', '" . (int)
$dp[Cache::PRIORITY] .
"');";
220: if (!sqlite_exec($db, "BEGIN; DELETE FROM cache WHERE file = '$dbFile'; $query COMMIT;")) {
229: $head =
'<?php //netteCache[01]' .
str_pad((string)
strlen($head), 6, '0', STR_PAD_LEFT) .
$head;
238: if (fwrite($handle, $data, $dataLen) !==
$dataLen) {
243: if (fwrite($handle, $head, $headLen) !==
$headLen) {
251: $this->delete($cacheFile, $handle);
257: * Removes item from the cache.
269: * Removes items from the cache by conditions & garbage collector.
270: * @param array conditions
275: $all =
!empty($conds[Cache::ALL]);
276: $collector =
empty($conds);
278: // cleaning using file iterator
279: if ($all ||
$collector) {
281: $base =
$this->dir .
DIRECTORY_SEPARATOR .
'c';
282: $iterator =
new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->dir), RecursiveIteratorIterator::CHILD_FIRST);
283: foreach ($iterator as $entry) {
284: $path = (string)
$entry;
288: if ($entry->isDir()) { // collector: remove empty dirs
293: $this->delete($path);
295: } else { // collector
297: if (!$meta) continue;
299: if (!empty($meta[self::META_EXPIRE]) &&
$meta[self::META_EXPIRE] <
$now) {
300: $this->delete($path, $meta[self::HANDLE]);
314: // cleaning using journal
317: foreach ((array)
$conds[Cache::TAGS] as $tag) {
324: $query[] =
"priority <= " . (int)
$conds[Cache::PRIORITY];
327: if (isset($query)) {
331: foreach ($files as $file) {
332: $this->delete($file);
341: * Reads cache data from disk.
342: * @param string file path
343: * @param int lock mode
344: * @return array|NULL
348: $handle =
@fopen($file, 'r+b'); // intentionally @
349: if (!$handle) return NULL;
354: if ($head &&
strlen($head) ===
self::META_HEADER_LEN) {
359: fseek($handle, $size +
self::META_HEADER_LEN); // needed by PHP < 5.2.6
360: $meta[self::FILE] =
$file;
361: $meta[self::HANDLE] =
$handle;
373: * Reads cache data from disk and closes cache file handle.
382: if (empty($meta[self::META_SERIALIZED])) {
392: * Returns file name.
398: if ($this->useDirs) {
409: * Deletes and closes file.
414: private static function delete($file, $handle =
NULL)
422: $handle =
@fopen($file, 'r+'); // intentionally @
428: @unlink($file); // intentionally @; not atomic
435: * Returns SQLite resource.
440: if ($this->db ===
NULL) {
445: @sqlite_exec($this->db, 'CREATE TABLE cache (file VARCHAR NOT NULL, priority, tag VARCHAR);
446: CREATE INDEX IDX_FILE ON cache (file); CREATE INDEX IDX_PRI ON cache (priority); CREATE INDEX IDX_TAG ON cache (tag);'); // intentionally @