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