1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Nette\Caching;
13:
14: use Nette;
15:
16:
17:
18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49:
50: class FileJournal extends Nette\Object implements ICacheJournal
51: {
52: const
53: MAGIC = 0x666a3030, 54: FILE = 'fj',
55: EXTNEW = '.new',
56: EXTLOG = '.log',
57: EXTLOGNEW = '.log.new',
58: LOGMAXSIZE = 65536, 59: INT32 = 4,
60: TAGS = 0x74616773, 61: PRIORITY = 0x7072696f, 62: ENTRIES = 0x656e7473, 63: DELETE = 'd',
64: ADD = 'a',
65: CLEAN = 'c';
66:
67:
68: private static $ops = array(
69: self::ADD => self::DELETE,
70: self::DELETE => self::ADD
71: );
72:
73:
74: private $file;
75:
76:
77: private $handle;
78:
79:
80: private $mtime = 0;
81:
82:
83: private $sections = array();
84:
85:
86: private $logHandle;
87:
88:
89: private $isLogNew = FALSE;
90:
91:
92: private $logMerge = array();
93:
94:
95: private $logMergeP = 0;
96:
97: 98: 99: 100:
101: public function __construct($dir)
102: {
103: $this->file = $dir . '/' . self::FILE;
104: $this->open();
105: }
106:
107:
108:
109: 110: 111:
112: public function __destruct()
113: {
114: if ($this->handle) {
115: fclose($this->handle);
116: }
117:
118: if ($this->logHandle) {
119: fclose($this->logHandle);
120: }
121: }
122:
123:
124:
125: 126: 127:
128: private function reload()
129: {
130: if (($mtime = @filemtime($this->file)) === FALSE) {
131: $mtime = 0;
132: }
133:
134: if ($this->mtime < $mtime) {
135: fclose($this->handle);
136: fclose($this->logHandle);
137: $this->handle = $this->logHandle = NULL;
138: $this->open();
139: }
140:
141: $this->logMerge = $this->mergeLogFile($this->logHandle, $this->logMergeP, $this->logMerge);
142: }
143:
144:
145:
146: 147: 148:
149: private function open()
150: {
151: $this->handle = $this->logHandle = NULL;
152: $this->mtime = $this->logMergeP = 0;
153: $this->sections = $this->logMerge = array();
154:
155: clearstatcache();
156: if (($this->mtime = @filemtime($this->file)) === FALSE) {
157: $this->mtime = 0;
158: }
159:
160: $tries = 3;
161: do {
162: if (!$tries--) {
163: throw new \InvalidStateException("Cannot open journal file '$this->file'.");
164: }
165:
166: if (!($this->handle = @fopen($this->file, 'rb'))) { 167: 168: $this->handle = NULL;
169:
170: } else {
171: list(,$magic, $sectionCount) = unpack('N2', fread($this->handle, 2 * self::INT32));
172:
173: if ($magic !== self::MAGIC) {
174: fclose($this->handle);
175: throw new \InvalidStateException("Malformed journal file '$this->file'.");
176: }
177:
178: for ($i = 0; $i < $sectionCount; ++$i) {
179: list(,$name, $offset, $keyLength, $keyCount) =
180: unpack('N4', fread($this->handle, 4 * self::INT32));
181:
182: $this->sections[$name] = (object) array(
183: 'offset' => $offset,
184: 'keyLength' => $keyLength,
185: 'keyCount' => $keyCount,
186: );
187: }
188: }
189:
190: 191: clearstatcache();
192: if (($mtime = @filemtime($this->file)) === FALSE) {
193: $mtime = 0;
194: }
195: } while ($this->mtime < $mtime);
196:
197:
198: if (!($this->logHandle = @fopen($logfile = $this->file . self::EXTLOG, 'a+b'))) { 199: throw new \InvalidStateException("Cannot open logfile '$logfile' for journal.");
200: }
201:
202: $doMergeFirst = FALSE;
203: $openNewLog = FALSE;
204: $reopen = FALSE;
205: if (flock($this->logHandle, LOCK_SH | LOCK_NB)) {
206: if (file_exists($logfile = $this->file . self::EXTLOGNEW)) {
207: if (($logmtime = @filemtime($this->file . self::EXTLOG)) === FALSE) {
208: throw new \InvalidStateException("Cannot determine modification time of logfile '$this->file" . self::EXTLOG . "'.");
209: }
210:
211: if ($logmtime < $this->mtime) {
212: 213: fclose($this->logHandle);
214: if (!@rename($this->file . self::EXTLOGNEW, $this->file . self::EXTLOG)) { 215: clearstatcache();
216: if (!file_exists($this->file . self::EXTLOGNEW)) {
217: 218: $reopen = TRUE;
219: } else {
220: 221: $openNewLog = TRUE;
222: }
223: } else {
224: 225: $reopen = TRUE;
226: }
227:
228: } else {
229: 230: if (!$this->rebuild()) {
231: $doMergeFirst = TRUE;
232: $openNewLog = TRUE;
233:
234: } 235: }
236:
237: } 238:
239: 240: 241:
242: } else {
243: 244: $doMergeFirst = TRUE;
245: $openNewLog = TRUE;
246: }
247:
248: if ($reopen && $openNewLog) {
249: throw new \LogicException('Something bad with algorithm.');
250: }
251:
252: if ($doMergeFirst) {
253: $this->logMerge = $this->mergeLogFile($this->logHandle, 0);
254: }
255:
256: if ($reopen) {
257: fclose($this->logHandle);
258: if (!($this->logHandle = @fopen($logfile = $this->file . self::EXTLOG, 'a+b'))) {
259: throw new \InvalidStateException("Cannot open logfile '$logfile'.");
260: }
261:
262: if (!flock($this->logHandle, LOCK_SH)) {
263: throw new \InvalidStateException('Cannot acquite shared lock on log.');
264: }
265: }
266:
267: if ($openNewLog) {
268: fclose($this->logHandle);
269: if (!($this->logHandle = @fopen($logfile = $this->file . self::EXTLOGNEW, 'a+b'))) { 270: throw new \InvalidStateException("Cannot open logfile '$logfile'.");
271: }
272:
273: $this->isLogNew = TRUE;
274: }
275:
276: $this->logMerge = $this->mergeLogFile($this->logHandle, 0, $this->logMerge);
277: $this->logMergeP = ftell($this->logHandle);
278:
279: 280: if ($this->logMergeP === 0) {
281: if (!flock($this->logHandle, LOCK_EX)) {
282: throw new \InvalidStateException('Cannot acquite exclusive lock on log.');
283: }
284:
285: $data = serialize(array());
286: $data = pack('N', strlen($data)) . $data;
287: $written = fwrite($this->logHandle, $data);
288: if ($written === FALSE || $written !== strlen($data)) {
289: throw new \InvalidStateException('Cannot write empty packet to log.');
290: }
291:
292: if (!flock($this->logHandle, LOCK_SH)) {
293: throw new \InvalidStateException('Cannot acquite shared lock on log.');
294: }
295: }
296: }
297:
298:
299:
300: 301: 302: 303: 304: 305:
306: public function write($key, array $dependencies)
307: {
308: $log = array();
309: $delete = $this->get(self::ENTRIES, $key);
310:
311: if ($delete !== NULL && isset($delete[$key])) {
312: foreach ($delete[$key] as $id) {
313: list($sectionName, $k) = explode(':', $id, 2);
314: $sectionName = intval($sectionName);
315: if (!isset($log[$sectionName])) {
316: $log[$sectionName] = array();
317: }
318:
319: if (!isset($log[$sectionName][self::DELETE])) {
320: $log[$sectionName][self::DELETE] = array();
321: }
322:
323: $log[$sectionName][self::DELETE][$k][] = $key;
324: }
325: }
326:
327: if (!empty($dependencies[Cache::TAGS])) {
328: if (!isset($log[self::TAGS])) {
329: $log[self::TAGS] = array();
330: }
331:
332: if (!isset($log[self::TAGS][self::ADD])) {
333: $log[self::TAGS][self::ADD] = array();
334: }
335:
336: foreach ((array) $dependencies[Cache::TAGS] as $tag) {
337: $log[self::TAGS][self::ADD][$tag] = (array) $key;
338: }
339: }
340:
341: if (!empty($dependencies[Cache::PRIORITY])) {
342: if (!isset($log[self::PRIORITY])) {
343: $log[self::PRIORITY] = array();
344: }
345:
346: if (!isset($log[self::PRIORITY][self::ADD])) {
347: $log[self::PRIORITY][self::ADD] = array();
348: }
349:
350: $log[self::PRIORITY][self::ADD][sprintf('%010u', (int) $dependencies[Cache::PRIORITY])] = (array) $key;
351: }
352:
353: if (empty($log)) {
354: return ;
355: }
356:
357: $entriesSection = array(self::ADD => array());
358:
359: foreach ($log as $sectionName => $section) {
360: if (!isset($section[self::ADD])) {
361: continue;
362: }
363:
364: foreach ($section[self::ADD] as $k => $_) {
365: $entriesSection[self::ADD][$key][] = "$sectionName:$k";
366: }
367: }
368:
369: $entriesSection[self::ADD][$key][] = self::ENTRIES . ':' . $key;
370: $log[self::ENTRIES] = $entriesSection;
371:
372: $this->log($log);
373: }
374:
375:
376:
377: 378: 379: 380: 381:
382: private function log(array $data)
383: {
384: $data = $this->mergeLogRecords(array(), $data);
385:
386: 387: 388: $data = serialize($data);
389: $data = pack('N', strlen($data)) . $data;
390:
391: $written = fwrite($this->logHandle, $data);
392: if ($written === FALSE || $written !== strlen($data)) {
393: throw new \InvalidStateException('Cannot write to log.');
394: }
395:
396:
397: 398: if (!$this->isLogNew) {
399: fseek($this->logHandle, 0, SEEK_END);
400: $size = ftell($this->logHandle);
401: if ($size > self::LOGMAXSIZE) {
402: $this->rebuild();
403: }
404: }
405:
406: return TRUE;
407: }
408:
409:
410:
411: 412: 413: 414:
415: private function rebuild()
416: {
417: if (!flock($this->logHandle, LOCK_EX | LOCK_NB)) {
418: 419: return TRUE;
420: }
421:
422: if (!($newhandle = @fopen($this->file . self::EXTNEW, 'wb'))) { 423: flock($this->logHandle, LOCK_UN);
424: return FALSE;
425: }
426:
427: 428: $merged = $this->mergeLogFile($this->logHandle);
429:
430: $sections = array_unique(
431: array_merge(array_keys($this->sections), array_keys($merged)),
432: SORT_NUMERIC
433: );
434: sort($sections);
435:
436: 437: $offset = 4096; 438: $newsections = array();
439:
440: foreach ($sections as $section) {
441: $maxKeyLength = 0;
442: $keyCount = 0;
443:
444: if (isset($this->sections[$section])) {
445: $maxKeyLength = $this->sections[$section]->keyLength;
446: $keyCount = $this->sections[$section]->keyCount;
447: }
448:
449: if (isset($merged[$section][self::ADD])) {
450: foreach ($merged[$section][self::ADD] as $k => $_) {
451: if (($len = strlen((string) $k)) > $maxKeyLength) {
452: $maxKeyLength = $len;
453: }
454:
455: $keyCount++; 456: }
457: }
458:
459: $newsections[$section] = (object) array(
460: 'keyLength' => $maxKeyLength,
461: 'keyCount' => $keyCount,
462: 'offset' => $offset,
463: );
464:
465: $offset += $keyCount * ($maxKeyLength + 2 * self::INT32);
466: }
467:
468: $dataOffset = $offset;
469: $dataWrite = array();
470: $clean = isset($merged[self::CLEAN]);
471: unset($merged[self::CLEAN]);
472:
473: 474: foreach ($sections as $section) {
475: fseek($newhandle, $newsections[$section]->offset, SEEK_SET);
476:
477: $pack = 'a' . $newsections[$section]->keyLength . 'NN';
478: $realKeyCount = 0;
479:
480: foreach (self::$ops as $op) {
481: if (isset($merged[$section][$op])) {
482: reset($merged[$section][$op]);
483: }
484: }
485:
486: if ($this->handle && isset($this->sections[$section]) && !$clean) {
487: $unpack = 'a' . $this->sections[$section]->keyLength . 'key/NvalueOffset/NvalueLength';
488: $recordSize = $this->sections[$section]->keyLength + 2 * self::INT32;
489: $batchSize = intval(65536 / $recordSize); 490: $i = 0;
491:
492: while ($i < $this->sections[$section]->keyCount) {
493: 494: fseek($this->handle, $this->sections[$section]->offset + $i * $recordSize, SEEK_SET);
495: $size = min($batchSize, $this->sections[$section]->keyCount - $i);
496: $data = stream_get_contents($this->handle, $size * $recordSize);
497:
498: if (!($data !== FALSE && strlen($data) === $size * $recordSize)) {
499: flock($this->logHandle, LOCK_UN);
500: fclose($newhandle);
501: return FALSE;
502: }
503:
504: 505: for ($j = 0; $j < $size && $i < $this->sections[$section]->keyCount; ++$j, ++$i) {
506: $record = (object) unpack($unpack, substr($data, $j * $recordSize, $recordSize));
507: $value = NULL;
508:
509: 510: if (isset($merged[$section][self::DELETE])) {
511:
512: 513: while (current($merged[$section][self::DELETE]) &&
514: strcmp(key($merged[$section][self::DELETE]), $record->key) < 0)
515: {
516: next($merged[$section][self::DELETE]);
517: }
518:
519: 520: if (strcmp(key($merged[$section][self::DELETE]), $record->key) === 0) {
521: fseek($this->handle, $record->valueOffset, SEEK_SET);
522: $value = @unserialize(fread($this->handle, $record->valueLength)); 523:
524: if ($value === FALSE) {
525: flock($this->logHandle, LOCK_UN);
526: fclose($newhandle);
527: return FALSE;
528: }
529:
530: $value = array_flip($value);
531: foreach (current($merged[$section][self::DELETE]) as $delete) {
532: unset($value[$delete]);
533: }
534: $value = array_keys($value);
535:
536: next($merged[$section][self::DELETE]);
537: }
538: }
539:
540: 541: if (isset($merged[$section][self::ADD])) {
542:
543: 544: while (current($merged[$section][self::ADD]) &&
545: strcmp(key($merged[$section][self::ADD]), $record->key) < 0)
546: {
547: $dataWrite[] = ($serialized = serialize(current($merged[$section][self::ADD])));
548: $packed = pack($pack, key($merged[$section][self::ADD]), $dataOffset, strlen($serialized));
549:
550: if (!$this->writeAll($newhandle, $packed)) {
551: flock($this->logHandle, LOCK_UN);
552: fclose($newhandle);
553: return FALSE;
554: }
555:
556: $realKeyCount++;
557: $dataOffset += strlen($serialized);
558: next($merged[$section][self::ADD]);
559: }
560:
561: 562: if (strcmp(key($merged[$section][self::ADD]), $record->key) === 0) {
563:
564: 565: if ($value === NULL) {
566: $value = $this->loadValue($this->handle, $record->valueOffset, $record->valueLength);
567: }
568:
569: if ($value === NULL) {
570: flock($this->logHandle, LOCK_UN);
571: fclose($newhandle);
572: return FALSE;
573: }
574:
575: $value = array_unique(array_merge(
576: $value,
577: current($merged[$section][self::ADD])
578: ));
579:
580: sort($value);
581:
582: next($merged[$section][self::ADD]);
583: }
584: }
585:
586:
587: if (is_array($value) && !empty($value) || $value === NULL) {
588: if ($value !== NULL) {
589: $dataWrite[] = ($serialized = serialize($value));
590: $newValueLength = strlen($serialized);
591:
592: } else {
593: $dataWrite[] = array($record->valueOffset, $record->valueLength);
594: $newValueLength = $record->valueLength;
595: }
596:
597: if (!$this->writeAll($newhandle, pack($pack, $record->key, $dataOffset, $newValueLength))) {
598: flock($this->logHandle, LOCK_UN);
599: fclose($newhandle);
600: return FALSE;
601: }
602:
603: $realKeyCount++;
604: $dataOffset += $newValueLength;
605: }
606: }
607: }
608: }
609:
610: while (isset($merged[$section][self::ADD]) && current($merged[$section][self::ADD])) {
611: $dataWrite[] = ($serialized = serialize(current($merged[$section][self::ADD])));
612: $valueLength = strlen($serialized);
613: $packed = pack($pack, key($merged[$section][self::ADD]), $dataOffset, $valueLength);
614:
615: if (!$this->writeAll($newhandle, $packed)) {
616: flock($this->logHandle, LOCK_UN);
617: fclose($newhandle);
618: return FALSE;
619: }
620:
621: $realKeyCount++;
622: $dataOffset += $valueLength;
623: next($merged[$section][self::ADD]);
624: }
625:
626: $newsections[$section]->keyCount = $realKeyCount;
627:
628: if ($realKeyCount < 1) {
629: unset($newsections[$section]);
630: }
631: }
632:
633: 634: fseek($newhandle, 0, SEEK_SET);
635: $data = pack('NN', self::MAGIC, count($newsections));
636: foreach ($newsections as $name => $section) {
637: $data .= pack('NNNN', $name, $section->offset, $section->keyLength, $section->keyCount);
638: }
639:
640: if (!$this->writeAll($newhandle, $data)) {
641: flock($this->logHandle, LOCK_UN);
642: fclose($newhandle);
643: return FALSE;
644: }
645:
646: 647: fseek($newhandle, $offset, SEEK_SET);
648: reset($dataWrite);
649:
650: while (!empty($dataWrite)) {
651: $data = array_shift($dataWrite);
652: if (is_string($data)) {
653:
654: 655: while (is_string(current($dataWrite))) {
656: $data .= array_shift($dataWrite);
657: }
658:
659: if (!$this->writeAll($newhandle, $data)) {
660: flock($this->logHandle, LOCK_UN);
661: fclose($newhandle);
662: return FALSE;
663: }
664: } else {
665: if (!is_array($data)) {
666: throw new \LogicException('Something bad with algorithm, it has to be array.');
667: }
668:
669: list($readOffset, $readLength) = $data;
670:
671: 672: while (!empty($dataWrite) && is_array(current($dataWrite))) {
673: list($nextReadOffset, $nextReadLength) = current($dataWrite);
674:
675: if ($readOffset + $readLength !== $nextReadOffset) {
676: break;
677: }
678:
679: $readLength += $nextReadLength;
680: array_shift($dataWrite);
681: }
682:
683: fseek($this->handle, $readOffset, SEEK_SET);
684:
685: while (($readLength -=
686: stream_copy_to_stream($this->handle, $newhandle, $readLength)) > 0);
687: }
688: }
689:
690:
691: 692:
693: fflush($newhandle); 694: fclose($newhandle);
695: $newhandle = NULL;
696:
697: if ($this->handle) {
698: fclose($this->handle);
699: $this->handle = NULL;
700: }
701:
702: if (!@rename($this->file . self::EXTNEW, $this->file)) { 703: flock($this->logHandle, LOCK_UN);
704: return FALSE;
705: }
706:
707: ftruncate($this->logHandle, 4 + strlen(serialize(array()))); 708: flock($this->logHandle, LOCK_UN);
709: fclose($this->logHandle);
710:
711: if (!@rename($this->file . self::EXTLOGNEW, $this->file . self::EXTLOG) && 712: file_exists($this->file . self::EXTLOGNEW))
713: {
714: $this->isLogNew = TRUE;
715: $logfile = $this->file . self::EXTLOGNEW;
716:
717: } else {
718: 719: $logfile = $this->file . self::EXTLOG;
720: }
721:
722: if (!($this->logHandle = @fopen($logfile, 'a+b'))) { 723: throw new \InvalidStateException("Cannot reopen logfile '$logfile'.");
724: }
725:
726: $this->logMerge = array();
727: $this->logMergeP = 0;
728:
729: if (!($this->handle = @fopen($this->file, 'rb'))) {
730: throw new \InvalidStateException("Cannot reopen file '$this->file'.");
731: }
732:
733: clearstatcache();
734: $this->mtime = (int) @filemtime($this->file);
735: $this->sections = $newsections;
736:
737: return TRUE;
738: }
739:
740:
741:
742: 743: 744: 745: 746: 747:
748: private function writeAll($handle, $data)
749: {
750: $bytesLeft = strlen($data);
751:
752: while ($bytesLeft > 0) {
753: $written = fwrite($handle, substr($data, strlen($data) - $bytesLeft));
754: if ($written === FALSE) {
755: return FALSE;
756: }
757: $bytesLeft -= $written;
758: }
759:
760: return TRUE;
761: }
762:
763:
764:
765: 766: 767: 768: 769: 770: 771:
772: private function loadValue($handle, $offset, $length)
773: {
774: fseek($handle, $offset, SEEK_SET);
775: $data = '';
776: $bytesLeft = $length;
777:
778: while ($bytesLeft > 0) {
779: $read = fread($handle, $bytesLeft);
780: if ($read === FALSE) {
781: return NULL;
782: }
783:
784: $data .= $read;
785: $bytesLeft -= strlen($read);
786: }
787:
788: $value = @unserialize($data); 789:
790: if ($value === FALSE) {
791: return NULL;
792: }
793:
794: return $value;
795: }
796:
797:
798:
799: 800: 801: 802: 803:
804: private function mergeLogFile($handle, $startp = 0, $merged = array())
805: {
806: fseek($handle, $startp, SEEK_SET);
807:
808: while (!feof($handle) && strlen($data = fread($handle, self::INT32)) === self::INT32) {
809: list(,$size) = unpack('N', $data);
810: $data = @unserialize(fread($handle, $size)); 811:
812: if ($data === FALSE) {
813: continue; 814: }
815:
816: $merged = $this->mergeLogRecords($merged, $data);
817: }
818:
819: ksort($merged);
820:
821: return $merged;
822: }
823:
824:
825:
826: 827: 828: 829: 830: 831:
832: private function mergeLogRecords(array $a, array $b)
833: {
834: $clean = isset($a[self::CLEAN]);
835: unset($a[self::CLEAN], $b[self::CLEAN]);
836:
837: if (isset($b[self::CLEAN])) {
838: return $b;
839: }
840:
841: foreach ($b as $section => $data) {
842: if (!isset($a[$section])) {
843: $a[$section] = array();
844: }
845:
846: foreach (self::$ops as $op) {
847: if (!isset($data[$op])) {
848: continue;
849: }
850:
851: if (!isset($a[$section][$op])) {
852: $a[$section][$op] = array();
853: }
854:
855: foreach ($data[$op] as $k => $v) {
856: if (!isset($a[$section][$op][$k])) {
857: $a[$section][$op][$k] = array();
858: }
859:
860: $a[$section][$op][$k] = array_unique(array_merge(
861: $a[$section][$op][$k],
862: $v
863: ));
864:
865: if (isset($a[$section][self::$ops[$op]][$k])) {
866: $a[$section][self::$ops[$op]][$k] =
867: array_flip($a[$section][self::$ops[$op]][$k]);
868:
869: foreach ($v as $unsetk) {
870: unset($a[$section][self::$ops[$op]][$k][$unsetk]);
871: }
872:
873: $a[$section][self::$ops[$op]][$k] =
874: array_keys($a[$section][self::$ops[$op]][$k]);
875: }
876: }
877: }
878:
879: foreach (self::$ops as $op) {
880: if (!isset($a[$section][$op])) {
881: continue;
882: }
883:
884: foreach ($a[$section][$op] as $k => $v) {
885: if (empty($v)) {
886: unset($a[$section][$op][$k]);
887: continue;
888: }
889:
890: sort($a[$section][$op][$k]);
891: }
892:
893: if (empty($a[$section][$op])) {
894: unset($a[$section][$op]);
895: continue;
896: }
897:
898: ksort($a[$section][$op]);
899: }
900: }
901:
902: if ($clean) {
903: $a[self::CLEAN] = TRUE;
904: }
905:
906: return $a;
907: }
908:
909:
910:
911: 912: 913: 914: 915:
916: public function clean(array $conditions)
917: {
918: if (!empty($conditions[Cache::ALL])) {
919: $this->log(array(self::CLEAN => TRUE));
920: return NULL;
921:
922: } else {
923: $log = array();
924: $entries = array();
925:
926: if (!empty($conditions[Cache::TAGS])) {
927: $tagEntries = array();
928:
929: foreach ((array) $conditions[Cache::TAGS] as $tag) {
930: $tagEntries = array_merge($tagEntries, $tagEntry = $this->get(self::TAGS, $tag));
931:
932: if (isset($tagEntry[$tag])) {
933: foreach ($tagEntry[$tag] as $entry) {
934: $entries[] = $entry;
935: }
936: }
937: }
938:
939: if (!empty($tagEntries)) {
940: if (!isset($log[self::TAGS])) {
941: $log[self::TAGS] = array();
942: }
943:
944: $log[self::TAGS][self::DELETE] = $tagEntries;
945: }
946: }
947:
948: if (isset($conditions[Cache::PRIORITY])) {
949: $priorityEntries = $this->getLte(self::PRIORITY, sprintf('%010u', (int) $conditions[Cache::PRIORITY]));
950: foreach ($priorityEntries as $priorityEntry) {
951: foreach ($priorityEntry as $entry) {
952: $entries[] = $entry;
953: }
954: }
955:
956: if (!empty($priorityEntries)) {
957: if (!isset($log[self::PRIORITY])) {
958: $log[self::PRIORITY] = array();
959: }
960:
961: $log[self::PRIORITY][self::DELETE] = $priorityEntries;
962: }
963: }
964:
965: if (!empty($log)) {
966: if (!$this->log($log)) {
967: return array();
968: }
969: }
970:
971: return array_values(array_unique($entries));
972: }
973: }
974:
975:
976:
977: 978: 979: 980: 981: 982:
983: private function get($section, $key)
984: {
985: $this->reload();
986:
987: $ret = $this->logMerge;
988:
989: if (!isset($ret[self::CLEAN])) {
990: list($offset, $record) = $this->lowerBound($section, $key);
991:
992: if ($offset !== -1 && $record->key === $key && !isset($ret[self::CLEAN])) {
993: $entries = $this->loadValue($this->handle, $record->valueOffset, $record->valueLength);
994:
995: $ret = $this->mergeLogRecords(
996: array($section => array(self::ADD => array($key => $entries))),
997: $ret
998: );
999: }
1000: }
1001:
1002: return isset($ret[$section][self::ADD][$key])
1003: ? array($key => $ret[$section][self::ADD][$key])
1004: : array();
1005: }
1006:
1007:
1008:
1009: 1010: 1011: 1012: 1013: 1014:
1015: private function getLte($section, $key)
1016: {
1017: $this->reload();
1018: $ret = array();
1019:
1020: if (!isset($this->logMerge[self::CLEAN])) {
1021: list($offset, $record) = $this->lowerBound($section, $key);
1022:
1023: if ($offset !== -1) {
1024: $unpack = 'a' . $this->sections[$section]->keyLength . 'key/NvalueOffset/NvalueLength';
1025: $recordSize = $this->sections[$section]->keyLength + 2 * self::INT32;
1026: $batchSize = intval(65536 / $recordSize);
1027: $i = 0;
1028: $count = ($offset - $this->sections[$section]->offset) / $recordSize;
1029:
1030: if ($record->key === $key) {
1031: $count += 1;
1032: }
1033:
1034: while ($i < $count) {
1035: 1036: fseek($this->handle, $this->sections[$section]->offset + $i * $recordSize, SEEK_SET);
1037: $size = min($batchSize, $count - $i);
1038: $data = stream_get_contents($this->handle, $size * $recordSize);
1039:
1040: if (!($data !== FALSE && strlen($data) === $size * $recordSize)) {
1041: return NULL;
1042: }
1043:
1044: 1045: for ($j = 0; $j < $size && $i < $count; ++$j, ++$i) {
1046: $record = (object) unpack($unpack, substr($data, $j * $recordSize, $recordSize));
1047: $ret[$record->key] = $this->loadValue($this->handle, $record->valueOffset, $record->valueLength);
1048:
1049: if ($ret[$record->key] === NULL) {
1050: unset($ret[$record->key]);
1051: }
1052: }
1053: }
1054: }
1055: }
1056:
1057: if (isset($this->logMerge[$section][self::DELETE])) {
1058: $ret = $this->mergeLogRecords(
1059: array($section => array(self::DELETE => $this->logMerge[$section][self::DELETE])),
1060: array($section => array(self::ADD => $ret))
1061: );
1062:
1063: if (!isset($ret[$section][self::ADD])) {
1064: $ret = array();
1065:
1066: } else {
1067: $ret = $ret[$section][self::ADD];
1068: }
1069: }
1070:
1071: if (isset($this->logMerge[$section][self::ADD])) {
1072: foreach ($this->logMerge[$section][self::ADD] as $k => $v) {
1073: if (strcmp($k, $key) > 0) {
1074: continue;
1075: }
1076:
1077: if (!isset($ret[$k])) {
1078: $ret[$k] = array();
1079: }
1080:
1081: $ret[$k] = array_values(array_unique(array_merge($ret[$k], $v)));
1082: }
1083: }
1084:
1085: return $ret;
1086: }
1087:
1088:
1089:
1090: 1091: 1092: 1093: 1094: 1095: 1096:
1097: private function lowerBound($section, $key)
1098: {
1099: if (!isset($this->sections[$section])) {
1100: return array(-1, NULL);
1101: }
1102:
1103: $l = 0;
1104: $r = $this->sections[$section]->keyCount;
1105: $unpack = 'a' . $this->sections[$section]->keyLength . 'key/NvalueOffset/NvalueLength';
1106: $recordSize = $this->sections[$section]->keyLength + 2 * self::INT32;
1107:
1108: while ($l < $r) {
1109: $m = intval(($l + $r) / 2);
1110: fseek($this->handle, $this->sections[$section]->offset + $m * $recordSize);
1111: $data = stream_get_contents($this->handle, $recordSize);
1112:
1113: if (!($data !== FALSE && strlen($data) === $recordSize)) {
1114: return array(-1, NULL);
1115: }
1116:
1117: $record = (object) unpack($unpack, $data);
1118:
1119: if (strcmp($record->key, $key) < 0) {
1120: $l = $m + 1;
1121: } else {
1122: $r = $m;
1123: }
1124: }
1125:
1126: fseek($this->handle, $this->sections[$section]->offset + $l * $recordSize);
1127: $data = stream_get_contents($this->handle, $recordSize);
1128:
1129: if (!($data !== FALSE && strlen($data) === $recordSize)) {
1130: return array(-1, NULL);
1131: }
1132:
1133: $record = (object) unpack($unpack, $data);
1134:
1135: return array(
1136: $this->sections[$section]->offset + $l * $recordSize,
1137: $record,
1138: );
1139: }
1140: }
1141: