1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
16: class Horde_Imap_Client_Cache
17: {
18:
19: const VERSION = 2;
20:
21: 22: 23: 24: 25: 26: 27:
28: static protected $_instances = array();
29:
30: 31: 32: 33: 34:
35: protected $_base;
36:
37: 38: 39: 40: 41:
42: protected $_cache;
43:
44: 45: 46: 47: 48: 49:
50: protected $_data = array();
51:
52: 53: 54: 55: 56:
57: protected $_loaded = array();
58:
59: 60: 61: 62: 63:
64: protected $_params = array();
65:
66: 67: 68: 69: 70:
71: protected $_slicemap = array();
72:
73: 74: 75: 76: 77: 78: 79: 80:
81: protected $_update = array();
82:
83: 84: 85: 86: 87: 88: 89: 90: 91:
92: static public function singleton($params = array())
93: {
94: ksort($params);
95: $sig = hash('md5', serialize($params));
96: if (!isset(self::$_instances[$sig])) {
97: self::$_instances[$sig] = new self($params);
98: }
99:
100: return self::$_instances[$sig];
101: }
102:
103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151:
152: public function __construct(array $params = array())
153: {
154:
155: $required = array('cacheob');
156: foreach ($required as $val) {
157: if (empty($params[$val])) {
158: throw new InvalidArgumentException('Missing required parameter for ' . __CLASS__ . ': ' . $val);
159: }
160: }
161:
162:
163: $params = array_merge(array(
164: 'debug' => false,
165: 'lifetime' => 604800,
166: 'slicesize' => 50
167: ), array_filter($params));
168:
169: if (isset($params['baseob'])) {
170: $this->_base = $params['baseob'];
171: $this->_params = array(
172: 'hostspec' => $this->_base->getParam('hostspec'),
173: 'port' => $this->_base->getParam('port'),
174: 'username' => $this->_base->getParam('username')
175: );
176: } else {
177: $this->_params = array(
178: 'hostspec' => $params['hostspec'],
179: 'port' => $params['port'],
180: 'username' => $params['username']
181: );
182: $params['debug'] = false;
183: }
184:
185: $this->_cache = $params['cacheob'];
186:
187: $this->_params['debug'] = (bool)$params['debug'];
188: $this->_params['lifetime'] = intval($params['lifetime']);
189: $this->_params['slicesize'] = intval($params['slicesize']);
190:
191: register_shutdown_function(array($this, 'shutdown'));
192: }
193:
194: 195: 196:
197: public function shutdown()
198: {
199: $lifetime = $this->_params['lifetime'];
200:
201: foreach ($this->_update as $mbox => $val) {
202: $s = &$this->_slicemap[$mbox];
203:
204: if (!empty($val['add'])) {
205: if ($s['c'] <= $this->_params['slicesize']) {
206: $val['slice'][] = $s['i'];
207: $this->_loadSlice($mbox, $s['i']);
208: }
209: $val['slicemap'] = true;
210:
211: foreach (array_keys(array_flip($val['add'])) as $uid) {
212: if ($s['c']++ > $this->_params['slicesize']) {
213: $s['c'] = 0;
214: $val['slice'][] = ++$s['i'];
215: $this->_loadSlice($mbox, $s['i']);
216: }
217: $s['s'][$uid] = $s['i'];
218: }
219: }
220:
221: if (!empty($val['slice'])) {
222: $d = &$this->_data[$mbox];
223: $val['slicemap'] = true;
224:
225: foreach (array_keys(array_flip($val['slice'])) as $slice) {
226: $data = array();
227: foreach (array_keys($s['s'], $slice) as $uid) {
228: $data[$uid] = is_array($d[$uid])
229: ? serialize($d[$uid])
230: : $d[$uid];
231: }
232: $this->_cache->set($this->_getCid($mbox, $slice), serialize($data), $lifetime);
233: }
234: }
235:
236: if (!empty($val['slicemap'])) {
237: $this->_cache->set($this->_getCid($mbox, 'slicemap'), serialize($s), $lifetime);
238: }
239: }
240: }
241:
242: 243: 244: 245: 246: 247: 248: 249:
250: protected function _getCid($mailbox, $slice)
251: {
252: return implode('|', array(
253: 'horde_imap_client',
254: $this->_params['username'],
255: $mailbox,
256: $this->_params['hostspec'],
257: $this->_params['port'],
258: $slice,
259: self::VERSION
260: ));
261: }
262:
263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278:
279: public function get($mailbox, array $uids = array(), $fields = array(),
280: $uidvalid = null)
281: {
282: $mailbox = strval($mailbox);
283:
284: if (empty($uids)) {
285: $this->_loadSliceMap($mailbox, $uidvalid);
286: return array_keys($this->_slicemap[$mailbox]['s']);
287: }
288:
289: $ret = array();
290: $this->_loadUids($mailbox, $uids, $uidvalid);
291:
292: if (empty($this->_data[$mailbox])) {
293: return $ret;
294: }
295:
296: if (!is_null($fields)) {
297: $fields = array_flip($fields);
298: }
299: $ptr = &$this->_data[$mailbox];
300:
301: foreach (array_intersect($uids, array_keys($ptr)) as $val) {
302: if (is_string($ptr[$val])) {
303: $ptr[$val] = @unserialize($ptr[$val]);
304: }
305:
306: $ret[$val] = (is_null($fields) || empty($ptr[$val]))
307: ? $ptr[$val]
308: : array_intersect_key($ptr[$val], $fields);
309: }
310:
311: if ($this->_params['debug'] && !empty($ret)) {
312: $this->_base->writeDebug('CACHE: Retrieved messages (mailbox: ' . $mailbox . '; UIDs: ' . $this->_base->getIdsOb(array_keys($ret))->tostring_sort . ")\n", Horde_Imap_Client::DEBUG_INFO);
313: }
314:
315: return $ret;
316: }
317:
318: 319: 320: 321: 322: 323: 324: 325: 326: 327:
328: public function set($mailbox, $data, $uidvalid)
329: {
330: $mailbox = strval($mailbox);
331:
332: if (empty($data)) {
333: $this->_loadSliceMap($mailbox, $uidvalid);
334: return;
335: }
336:
337: $update = array_keys($data);
338:
339: try {
340: $this->_loadUids($mailbox, $update, $uidvalid);
341: } catch (Horde_Imap_Client_Exception $e) {
342:
343: }
344:
345: $d = &$this->_data[$mailbox];
346: $s = &$this->_slicemap[$mailbox]['s'];
347: $add = $updated = array();
348:
349: foreach ($data as $k => $v) {
350: if (isset($d[$k])) {
351: if (is_string($d[$k])) {
352: $d[$k] = @unserialize($d[$k]);
353: }
354: $d[$k] = is_array($d[$k])
355: ? array_merge($d[$k], $v)
356: : $v;
357: if (isset($s[$k])) {
358: $updated[$s[$k]] = true;
359: }
360: } else {
361: $d[$k] = $v;
362: $add[] = $k;
363: }
364: }
365:
366: $this->_toUpdate($mailbox, 'add', $add);
367: $this->_toUpdate($mailbox, 'slice', array_keys($updated));
368:
369: if ($this->_params['debug']) {
370: $this->_base->writeDebug('CACHE: Stored messages (mailbox: ' . $mailbox . '; UIDs: ' . $this->_base->getIdsOb($update)->tostring_sort . ")\n", Horde_Imap_Client::DEBUG_INFO);
371: }
372: }
373:
374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386:
387: public function getMetaData($mailbox, $uidvalid = null,
388: array $entries = array())
389: {
390: $mailbox = strval($mailbox);
391: $this->_loadSliceMap($mailbox, $uidvalid);
392:
393: return empty($entries)
394: ? $this->_slicemap[$mailbox]['d']
395: : array_intersect_key($this->_slicemap[$mailbox]['d'], array_flip($entries));
396: }
397:
398: 399: 400: 401: 402: 403: 404: 405: 406: 407:
408: public function setMetaData($mailbox, $uidvalid, array $data = array())
409: {
410: unset($data['uidvalid']);
411: if (empty($data)) {
412: return;
413: }
414:
415: $mailbox = strval($mailbox);
416: $this->_loadSliceMap($mailbox, $uidvalid);
417: $this->_slicemap[$mailbox]['d'] = array_merge($this->_slicemap[$mailbox]['d'], $data);
418: $this->_toUpdate($mailbox, 'slicemap', true);
419: }
420:
421: 422: 423: 424: 425: 426:
427: public function deleteMsgs($mailbox, $uids)
428: {
429: if (empty($uids)) {
430: return;
431: }
432:
433: $mailbox = strval($mailbox);
434: $this->_loadSliceMap($mailbox);
435:
436: $slicemap = &$this->_slicemap[$mailbox];
437: $update = array_intersect_key($slicemap['s'], array_flip($uids));
438:
439: if (!empty($update)) {
440: $this->_loadUids($mailbox, array_keys($update));
441: $d = &$this->_data[$mailbox];
442:
443: foreach (array_keys($update) as $id) {
444: unset($d[$id], $slicemap['s'][$id]);
445: }
446:
447: foreach (array_unique($update) as $slice) {
448:
449: if (($slice != $slicemap['i']) &&
450: ($slice_uids = array_keys($slicemap['s'], $slice)) &&
451: ($this->_params['slicesize'] * 0.1) > count($slice_uids)) {
452: $this->_toUpdate($mailbox, 'add', $slice_uids);
453: $this->_cache->expire($this->_getCid($mailbox, $slice));
454: foreach ($slice_uids as $val) {
455: unset($slicemap['s'][$val]);
456: }
457: } else {
458: $this->_toUpdate($mailbox, 'slice', array($slice));
459: }
460: }
461:
462: if ($this->_params['debug']) {
463: $this->_base->writeDebug('CACHE: Deleted messages (mailbox: ' . $mailbox . '; UIDs: ' . $this->_base->getIdsOb(array_keys($update))->tostring_sort . ")\n", Horde_Imap_Client::DEBUG_INFO);
464: }
465: }
466: }
467:
468: 469: 470: 471: 472:
473: public function deleteMailbox($mbox)
474: {
475: $mbox = strval($mbox);
476: $this->_loadSliceMap($mbox);
477: $this->_deleteMailbox($mbox);
478: }
479:
480: 481: 482: 483: 484:
485: protected function _deleteMailbox($mbox)
486: {
487: foreach (array_merge(array_keys(array_flip($this->_slicemap[$mbox]['s'])), array('slicemap')) as $slice) {
488: $cid = $this->_getCid($mbox, $slice);
489: $this->_cache->expire($cid);
490: unset($this->_loaded[$cid]);
491: }
492:
493: unset(
494: $this->_data[$mbox],
495: $this->_slicemap[$mbox],
496: $this->_update[$mbox]
497: );
498:
499: if ($this->_params['debug']) {
500: $this->_base->writeDebug('CACHE: Deleted mailbox (mailbox: ' . $mbox . ")\n", Horde_Imap_Client::DEBUG_INFO);
501: }
502: }
503:
504: 505: 506: 507: 508: 509: 510:
511: protected function _loadUids($mailbox, $uids, $uidvalid = null)
512: {
513: if (!isset($this->_data[$mailbox])) {
514: $this->_data[$mailbox] = array();
515: }
516:
517: $this->_loadSliceMap($mailbox, $uidvalid);
518:
519: if (!empty($uids)) {
520: foreach (array_unique(array_intersect_key($this->_slicemap[$mailbox]['s'], array_flip($uids))) as $slice) {
521: $this->_loadSlice($mailbox, $slice);
522: }
523: }
524: }
525:
526: 527: 528: 529: 530: 531:
532: protected function _loadSlice($mailbox, $slice)
533: {
534: $cache_id = $this->_getCid($mailbox, $slice);
535:
536: if (!empty($this->_loaded[$cache_id])) {
537: return;
538: }
539:
540: if ((($data = $this->_cache->get($cache_id, $this->_params['lifetime'])) !== false) &&
541: ($data = @unserialize($data)) &&
542: is_array($data)) {
543: $this->_data[$mailbox] += $data;
544: $this->_loaded[$cache_id] = true;
545: } else {
546: $ptr = &$this->_slicemap[$mailbox];
547:
548:
549: foreach (array_keys($ptr['s'], $slice) as $val) {
550: unset($ptr['s'][$val]);
551: }
552:
553: if ($slice == $ptr['i']) {
554: $ptr['c'] = 0;
555: }
556: }
557: }
558:
559: 560: 561: 562: 563: 564: 565: 566:
567: protected function _loadSliceMap($mailbox, $uidvalid = null)
568: {
569: if (!isset($this->_slicemap[$mailbox]) &&
570: (($data = $this->_cache->get($this->_getCid($mailbox, 'slicemap'), $this->_params['lifetime'])) !== false) &&
571: ($slice = @unserialize($data)) &&
572: is_array($slice)) {
573: $this->_slicemap[$mailbox] = $slice;
574: }
575:
576: if (isset($this->_slicemap[$mailbox])) {
577: $ptr = &$this->_slicemap[$mailbox];
578: if (is_null($ptr['d']['uidvalid'])) {
579: $ptr['d']['uidvalid'] = $uidvalid;
580: return;
581: } elseif (!is_null($uidvalid) &&
582: ($ptr['d']['uidvalid'] != $uidvalid)) {
583: $this->_deleteMailbox($mailbox);
584: } else {
585: return;
586: }
587: }
588:
589: $this->_slicemap[$mailbox] = array(
590:
591: 'c' => 0,
592:
593:
594: 'd' => array('uidvalid' => $uidvalid),
595:
596: 'i' => 0,
597:
598: 's' => array()
599: );
600: }
601:
602: 603: 604: 605: 606: 607: 608:
609: protected function _toUpdate($mailbox, $type, $data)
610: {
611: if (!isset($this->_update[$mailbox])) {
612: $this->_update[$mailbox] = array(
613: 'add' => array(),
614: 'slice' => array()
615: );
616: }
617:
618: $this->_update[$mailbox][$type] = ($type == 'slicemap')
619: ? $data
620: : array_merge($this->_update[$mailbox][$type], $data);
621: }
622:
623: }
624: