Namespaces

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Config
      • Adapters
      • Extensions
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Diagnostics
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
      • PhpGenerator
  • NetteModule
  • None
  • PHP

Classes

  • DevNullStorage
  • FileJournal
  • FileStorage
  • MemcachedStorage
  • MemoryStorage
  • PhpFileStorage

Interfaces

  • IJournal
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (http://nette.org)
  5:  *
  6:  * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  7:  *
  8:  * For the full copyright and license information, please view
  9:  * the file license.txt that was distributed with this source code.
 10:  */
 11: 
 12: namespace Nette\Caching\Storages;
 13: 
 14: use Nette,
 15:     Nette\Caching\Cache;
 16: 
 17: 
 18: /**
 19:  * Cache file storage.
 20:  *
 21:  * @author     David Grudl
 22:  */
 23: class FileStorage extends Nette\Object implements Nette\Caching\IStorage
 24: {
 25:     /**
 26:      * Atomic thread safe logic:
 27:      *
 28:      * 1) reading: open(r+b), lock(SH), read
 29:      *     - delete?: delete*, close
 30:      * 2) deleting: delete*
 31:      * 3) writing: open(r+b || wb), lock(EX), truncate*, write data, write meta, close
 32:      *
 33:      * delete* = try unlink, if fails (on NTFS) { lock(EX), truncate, close, unlink } else close (on ext3)
 34:      */
 35: 
 36:     /** @internal cache file structure */
 37:     const META_HEADER_LEN = 28, // 22b signature + 6b meta-struct size + serialized meta-struct + data
 38:     // meta structure: array of
 39:         META_TIME = 'time', // timestamp
 40:         META_SERIALIZED = 'serialized', // is content serialized?
 41:         META_EXPIRE = 'expire', // expiration timestamp
 42:         META_DELTA = 'delta', // relative (sliding) expiration
 43:         META_ITEMS = 'di', // array of dependent items (file => timestamp)
 44:         META_CALLBACKS = 'callbacks'; // array of callbacks (function, args)
 45: 
 46:     /** additional cache structure */
 47:     const FILE = 'file',
 48:         HANDLE = 'handle';
 49: 
 50: 
 51:     /** @var float  probability that the clean() routine is started */
 52:     public static $gcProbability = 0.001;
 53: 
 54:     /** @var bool */
 55:     public static $useDirectories = TRUE;
 56: 
 57:     /** @var string */
 58:     private $dir;
 59: 
 60:     /** @var bool */
 61:     private $useDirs;
 62: 
 63:     /** @var IJournal */
 64:     private $journal;
 65: 
 66:     /** @var array */
 67:     private $locks;
 68: 
 69: 
 70:     public function __construct($dir, IJournal $journal = NULL)
 71:     {
 72:         $this->dir = realpath($dir);
 73:         if ($this->dir === FALSE) {
 74:             throw new Nette\DirectoryNotFoundException("Directory '$dir' not found.");
 75:         }
 76: 
 77:         $this->useDirs = (bool) static::$useDirectories;
 78:         $this->journal = $journal;
 79: 
 80:         if (mt_rand() / mt_getrandmax() < static::$gcProbability) {
 81:             $this->clean(array());
 82:         }
 83:     }
 84: 
 85: 
 86:     /**
 87:      * Read from cache.
 88:      * @param  string key
 89:      * @return mixed|NULL
 90:      */
 91:     public function read($key)
 92:     {
 93:         $meta = $this->readMetaAndLock($this->getCacheFile($key), LOCK_SH);
 94:         if ($meta && $this->verify($meta)) {
 95:             return $this->readData($meta); // calls fclose()
 96: 
 97:         } else {
 98:             return NULL;
 99:         }
100:     }
101: 
102: 
103:     /**
104:      * Verifies dependencies.
105:      * @param  array
106:      * @return bool
107:      */
108:     private function verify($meta)
109:     {
110:         do {
111:             if (!empty($meta[self::META_DELTA])) {
112:                 // meta[file] was added by readMetaAndLock()
113:                 if (filemtime($meta[self::FILE]) + $meta[self::META_DELTA] < time()) {
114:                     break;
115:                 }
116:                 touch($meta[self::FILE]);
117: 
118:             } elseif (!empty($meta[self::META_EXPIRE]) && $meta[self::META_EXPIRE] < time()) {
119:                 break;
120:             }
121: 
122:             if (!empty($meta[self::META_CALLBACKS]) && !Cache::checkCallbacks($meta[self::META_CALLBACKS])) {
123:                 break;
124:             }
125: 
126:             if (!empty($meta[self::META_ITEMS])) {
127:                 foreach ($meta[self::META_ITEMS] as $depFile => $time) {
128:                     $m = $this->readMetaAndLock($depFile, LOCK_SH);
129:                     if ($m[self::META_TIME] !== $time || ($m && !$this->verify($m))) {
130:                         break 2;
131:                     }
132:                 }
133:             }
134: 
135:             return TRUE;
136:         } while (FALSE);
137: 
138:         $this->delete($meta[self::FILE], $meta[self::HANDLE]); // meta[handle] & meta[file] was added by readMetaAndLock()
139:         return FALSE;
140:     }
141: 
142: 
143:     /**
144:      * Prevents item reading and writing. Lock is released by write() or remove().
145:      * @param  string key
146:      * @return void
147:      */
148:     public function lock($key)
149:     {
150:         $cacheFile = $this->getCacheFile($key);
151:         if ($this->useDirs && !is_dir($dir = dirname($cacheFile))) {
152:             @mkdir($dir); // @ - directory may already exist
153:         }
154:         $handle = @fopen($cacheFile, 'r+b'); // @ - file may not exist
155:         if (!$handle) {
156:             $handle = fopen($cacheFile, 'wb');
157:             if (!$handle) {
158:                 return;
159:             }
160:         }
161: 
162:         $this->locks[$key] = $handle;
163:         flock($handle, LOCK_EX);
164:     }
165: 
166: 
167:     /**
168:      * Writes item into the cache.
169:      * @param  string key
170:      * @param  mixed  data
171:      * @param  array  dependencies
172:      * @return void
173:      */
174:     public function write($key, $data, array $dp)
175:     {
176:         $meta = array(
177:             self::META_TIME => microtime(),
178:         );
179: 
180:         if (isset($dp[Cache::EXPIRATION])) {
181:             if (empty($dp[Cache::SLIDING])) {
182:                 $meta[self::META_EXPIRE] = $dp[Cache::EXPIRATION] + time(); // absolute time
183:             } else {
184:                 $meta[self::META_DELTA] = (int) $dp[Cache::EXPIRATION]; // sliding time
185:             }
186:         }
187: 
188:         if (isset($dp[Cache::ITEMS])) {
189:             foreach ((array) $dp[Cache::ITEMS] as $item) {
190:                 $depFile = $this->getCacheFile($item);
191:                 $m = $this->readMetaAndLock($depFile, LOCK_SH);
192:                 $meta[self::META_ITEMS][$depFile] = $m[self::META_TIME]; // may be NULL
193:                 unset($m);
194:             }
195:         }
196: 
197:         if (isset($dp[Cache::CALLBACKS])) {
198:             $meta[self::META_CALLBACKS] = $dp[Cache::CALLBACKS];
199:         }
200: 
201:         if (!isset($this->locks[$key])) {
202:             $this->lock($key);
203:             if (!isset($this->locks[$key])) {
204:                 return;
205:             }
206:         }
207:         $handle = $this->locks[$key];
208:         unset($this->locks[$key]);
209: 
210:         $cacheFile = $this->getCacheFile($key);
211: 
212:         if (isset($dp[Cache::TAGS]) || isset($dp[Cache::PRIORITY])) {
213:             if (!$this->journal) {
214:                 throw new Nette\InvalidStateException('CacheJournal has not been provided.');
215:             }
216:             $this->journal->write($cacheFile, $dp);
217:         }
218: 
219:         ftruncate($handle, 0);
220: 
221:         if (!is_string($data)) {
222:             $data = serialize($data);
223:             $meta[self::META_SERIALIZED] = TRUE;
224:         }
225: 
226:         $head = serialize($meta) . '?>';
227:         $head = '<?php //netteCache[01]' . str_pad((string) strlen($head), 6, '0', STR_PAD_LEFT) . $head;
228:         $headLen = strlen($head);
229:         $dataLen = strlen($data);
230: 
231:         do {
232:             if (fwrite($handle, str_repeat("\x00", $headLen), $headLen) !== $headLen) {
233:                 break;
234:             }
235: 
236:             if (fwrite($handle, $data, $dataLen) !== $dataLen) {
237:                 break;
238:             }
239: 
240:             fseek($handle, 0);
241:             if (fwrite($handle, $head, $headLen) !== $headLen) {
242:                 break;
243:             }
244: 
245:             flock($handle, LOCK_UN);
246:             fclose($handle);
247:             return;
248:         } while (FALSE);
249: 
250:         $this->delete($cacheFile, $handle);
251:     }
252: 
253: 
254:     /**
255:      * Removes item from the cache.
256:      * @param  string key
257:      * @return void
258:      */
259:     public function remove($key)
260:     {
261:         unset($this->locks[$key]);
262:         $this->delete($this->getCacheFile($key));
263:     }
264: 
265: 
266:     /**
267:      * Removes items from the cache by conditions & garbage collector.
268:      * @param  array  conditions
269:      * @return void
270:      */
271:     public function clean(array $conditions)
272:     {
273:         $all = !empty($conditions[Cache::ALL]);
274:         $collector = empty($conditions);
275: 
276:         // cleaning using file iterator
277:         if ($all || $collector) {
278:             $now = time();
279:             foreach (Nette\Utils\Finder::find('_*')->from($this->dir)->childFirst() as $entry) {
280:                 $path = (string) $entry;
281:                 if ($entry->isDir()) { // collector: remove empty dirs
282:                     @rmdir($path); // @ - removing dirs is not necessary
283:                     continue;
284:                 }
285:                 if ($all) {
286:                     $this->delete($path);
287: 
288:                 } else { // collector
289:                     $meta = $this->readMetaAndLock($path, LOCK_SH);
290:                     if (!$meta) {
291:                         continue;
292:                     }
293: 
294:                     if ((!empty($meta[self::META_DELTA]) && filemtime($meta[self::FILE]) + $meta[self::META_DELTA] < $now)
295:                         || (!empty($meta[self::META_EXPIRE]) && $meta[self::META_EXPIRE] < $now)
296:                     ) {
297:                         $this->delete($path, $meta[self::HANDLE]);
298:                         continue;
299:                     }
300: 
301:                     flock($meta[self::HANDLE], LOCK_UN);
302:                     fclose($meta[self::HANDLE]);
303:                 }
304:             }
305: 
306:             if ($this->journal) {
307:                 $this->journal->clean($conditions);
308:             }
309:             return;
310:         }
311: 
312:         // cleaning using journal
313:         if ($this->journal) {
314:             foreach ($this->journal->clean($conditions) as $file) {
315:                 $this->delete($file);
316:             }
317:         }
318:     }
319: 
320: 
321:     /**
322:      * Reads cache data from disk.
323:      * @param  string  file path
324:      * @param  int     lock mode
325:      * @return array|NULL
326:      */
327:     protected function readMetaAndLock($file, $lock)
328:     {
329:         $handle = @fopen($file, 'r+b'); // @ - file may not exist
330:         if (!$handle) {
331:             return NULL;
332:         }
333: 
334:         flock($handle, $lock);
335: 
336:         $head = stream_get_contents($handle, self::META_HEADER_LEN);
337:         if ($head && strlen($head) === self::META_HEADER_LEN) {
338:             $size = (int) substr($head, -6);
339:             $meta = stream_get_contents($handle, $size, self::META_HEADER_LEN);
340:             $meta = @unserialize($meta); // intentionally @
341:             if (is_array($meta)) {
342:                 fseek($handle, $size + self::META_HEADER_LEN); // needed by PHP < 5.2.6
343:                 $meta[self::FILE] = $file;
344:                 $meta[self::HANDLE] = $handle;
345:                 return $meta;
346:             }
347:         }
348: 
349:         flock($handle, LOCK_UN);
350:         fclose($handle);
351:         return NULL;
352:     }
353: 
354: 
355:     /**
356:      * Reads cache data from disk and closes cache file handle.
357:      * @param  array
358:      * @return mixed
359:      */
360:     protected function readData($meta)
361:     {
362:         $data = stream_get_contents($meta[self::HANDLE]);
363:         flock($meta[self::HANDLE], LOCK_UN);
364:         fclose($meta[self::HANDLE]);
365: 
366:         if (empty($meta[self::META_SERIALIZED])) {
367:             return $data;
368:         } else {
369:             return @unserialize($data); // intentionally @
370:         }
371:     }
372: 
373: 
374:     /**
375:      * Returns file name.
376:      * @param  string
377:      * @return string
378:      */
379:     protected function getCacheFile($key)
380:     {
381:         $file = urlencode($key);
382:         if ($this->useDirs && $a = strrpos($file, '%00')) { // %00 = urlencode(Nette\Caching\Cache::NAMESPACE_SEPARATOR)
383:             $file = substr_replace($file, '/_', $a, 3);
384:         }
385:         return $this->dir . '/_' . $file;
386:     }
387: 
388: 
389:     /**
390:      * Deletes and closes file.
391:      * @param  string
392:      * @param  resource
393:      * @return void
394:      */
395:     private static function delete($file, $handle = NULL)
396:     {
397:         if (@unlink($file)) { // @ - file may not already exist
398:             if ($handle) {
399:                 flock($handle, LOCK_UN);
400:                 fclose($handle);
401:             }
402:             return;
403:         }
404: 
405:         if (!$handle) {
406:             $handle = @fopen($file, 'r+'); // @ - file may not exist
407:         }
408:         if ($handle) {
409:             flock($handle, LOCK_EX);
410:             ftruncate($handle, 0);
411:             flock($handle, LOCK_UN);
412:             fclose($handle);
413:             @unlink($file); // @ - file may not already exist
414:         }
415:     }
416: 
417: }
418: 
Nette Framework 2.0.11 API API documentation generated by ApiGen 2.8.0