Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
      • Traits
    • Reflection
    • Security
    • Tokenizer
    • Utils
  • Tracy
    • Bridges
      • Nette
  • none

Classes

  • DevNullStorage
  • FileStorage
  • MemcachedStorage
  • MemoryStorage
  • NewMemcachedStorage
  • SQLiteJournal
  • SQLiteStorage

Interfaces

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