Differences Between: [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403]
1 <?php 2 /** 3 * Copyright 2005-2017 Horde LLC (http://www.horde.org/) 4 * 5 * See the enclosed file LICENSE for license information (LGPL). If you 6 * did not receive this file, see http://www.horde.org/licenses/lgpl21. 7 * 8 * @category Horde 9 * @copyright 2005-2017 Horde LLC 10 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 11 * @package Imap_Client 12 */ 13 14 /** 15 * A Horde_Cache implementation for caching IMAP/POP data. 16 * Requires the Horde_Cache package. 17 * 18 * @author Michael Slusarz <slusarz@horde.org> 19 * @category Horde 20 * @copyright 2005-2017 Horde LLC 21 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 22 * @package Imap_Client 23 */ 24 class Horde_Imap_Client_Cache_Backend_Cache 25 extends Horde_Imap_Client_Cache_Backend 26 { 27 /** Cache structure version. */ 28 const VERSION = 3; 29 30 /** 31 * The cache object. 32 * 33 * @var Horde_Cache 34 */ 35 protected $_cache; 36 37 /** 38 * The working data for the current pageload. All changes take place to 39 * this data. 40 * 41 * @var array 42 */ 43 protected $_data = array(); 44 45 /** 46 * The list of cache slices loaded. 47 * 48 * @var array 49 */ 50 protected $_loaded = array(); 51 52 /** 53 * The mapping of UIDs to slices. 54 * 55 * @var array 56 */ 57 protected $_slicemap = array(); 58 59 /** 60 * The list of items to update: 61 * - add: (array) List of IDs that were added. 62 * - slice: (array) List of slices that were modified. 63 * - slicemap: (boolean) Was slicemap info changed? 64 * 65 * @var array 66 */ 67 protected $_update = array(); 68 69 /** 70 * Constructor. 71 * 72 * @param array $params Configuration parameters: 73 * <pre> 74 * - REQUIRED Parameters: 75 * - cacheob: (Horde_Cache) The cache object to use. 76 * 77 * - Optional Parameters: 78 * - lifetime: (integer) The lifetime of the cache data (in seconds). 79 * DEFAULT: 1 week (604800 seconds) 80 * - slicesize: (integer) The slicesize to use. 81 * DEFAULT: 50 82 * </pre> 83 */ 84 public function __construct(array $params = array()) 85 { 86 // Default parameters. 87 $params = array_merge(array( 88 'lifetime' => 604800, 89 'slicesize' => 50 90 ), array_filter($params)); 91 92 if (!isset($params['cacheob'])) { 93 throw new InvalidArgumentException('Missing cacheob parameter.'); 94 } 95 96 foreach (array('lifetime', 'slicesize') as $val) { 97 $params[$val] = intval($params[$val]); 98 } 99 100 parent::__construct($params); 101 } 102 103 /** 104 * Initialization tasks. 105 */ 106 protected function _initOb() 107 { 108 $this->_cache = $this->_params['cacheob']; 109 register_shutdown_function(array($this, 'save')); 110 } 111 112 /** 113 * Updates the cache. 114 */ 115 public function save() 116 { 117 $lifetime = $this->_params['lifetime']; 118 119 foreach ($this->_update as $mbox => $val) { 120 $s = &$this->_slicemap[$mbox]; 121 122 try { 123 if (!empty($val['add'])) { 124 if ($s['c'] <= $this->_params['slicesize']) { 125 $val['slice'][] = $s['i']; 126 $this->_loadSlice($mbox, $s['i']); 127 } 128 $val['slicemap'] = true; 129 130 foreach (array_keys(array_flip($val['add'])) as $uid) { 131 if ($s['c']++ > $this->_params['slicesize']) { 132 $s['c'] = 0; 133 $val['slice'][] = ++$s['i']; 134 $this->_loadSlice($mbox, $s['i']); 135 } 136 $s['s'][$uid] = $s['i']; 137 } 138 } 139 140 if (!empty($val['slice'])) { 141 $d = &$this->_data[$mbox]; 142 $val['slicemap'] = true; 143 144 foreach (array_keys(array_flip($val['slice'])) as $slice) { 145 $data = array(); 146 foreach (array_keys($s['s'], $slice) as $uid) { 147 $data[$uid] = is_array($d[$uid]) 148 ? serialize($d[$uid]) 149 : $d[$uid]; 150 } 151 $this->_cache->set($this->_getCid($mbox, $slice), serialize($data), $lifetime); 152 } 153 } 154 155 if (!empty($val['slicemap'])) { 156 $this->_cache->set($this->_getCid($mbox, 'slicemap'), serialize($s), $lifetime); 157 } 158 } catch (Horde_Exception $e) { 159 } 160 } 161 162 $this->_update = array(); 163 } 164 165 /** 166 */ 167 public function get($mailbox, $uids, $fields, $uidvalid) 168 { 169 $ret = array(); 170 $this->_loadUids($mailbox, $uids, $uidvalid); 171 172 if (empty($this->_data[$mailbox])) { 173 return $ret; 174 } 175 176 if (!empty($fields)) { 177 $fields = array_flip($fields); 178 } 179 $ptr = &$this->_data[$mailbox]; 180 181 foreach (array_intersect($uids, array_keys($ptr)) as $val) { 182 if (is_string($ptr[$val])) { 183 try { 184 $ptr[$val] = @unserialize($ptr[$val]); 185 } catch (Exception $e) {} 186 } 187 188 $ret[$val] = (empty($fields) || empty($ptr[$val])) 189 ? $ptr[$val] 190 : array_intersect_key($ptr[$val], $fields); 191 } 192 193 return $ret; 194 } 195 196 /** 197 */ 198 public function getCachedUids($mailbox, $uidvalid) 199 { 200 $this->_loadSliceMap($mailbox, $uidvalid); 201 return array_unique(array_merge( 202 array_keys($this->_slicemap[$mailbox]['s']), 203 (isset($this->_update[$mailbox]) ? $this->_update[$mailbox]['add'] : array()) 204 )); 205 } 206 207 /** 208 */ 209 public function set($mailbox, $data, $uidvalid) 210 { 211 $update = array_keys($data); 212 213 try { 214 $this->_loadUids($mailbox, $update, $uidvalid); 215 } catch (Horde_Imap_Client_Exception $e) { 216 // Ignore invalidity - just start building the new cache 217 } 218 219 $d = &$this->_data[$mailbox]; 220 $s = &$this->_slicemap[$mailbox]['s']; 221 $add = $updated = array(); 222 223 foreach ($data as $k => $v) { 224 if (isset($d[$k])) { 225 if (is_string($d[$k])) { 226 try { 227 $d[$k] = @unserialize($d[$k]); 228 } catch (Exception $e) {} 229 } 230 $d[$k] = is_array($d[$k]) 231 ? array_merge($d[$k], $v) 232 : $v; 233 if (isset($s[$k])) { 234 $updated[$s[$k]] = true; 235 } 236 } else { 237 $d[$k] = $v; 238 $add[] = $k; 239 } 240 } 241 242 $this->_toUpdate($mailbox, 'add', $add); 243 $this->_toUpdate($mailbox, 'slice', array_keys($updated)); 244 } 245 246 /** 247 */ 248 public function getMetaData($mailbox, $uidvalid, $entries) 249 { 250 $this->_loadSliceMap($mailbox, $uidvalid); 251 252 return empty($entries) 253 ? $this->_slicemap[$mailbox]['d'] 254 : array_intersect_key($this->_slicemap[$mailbox]['d'], array_flip($entries)); 255 } 256 257 /** 258 */ 259 public function setMetaData($mailbox, $data) 260 { 261 $this->_loadSliceMap($mailbox, isset($data['uidvalid']) ? $data['uidvalid'] : null); 262 $this->_slicemap[$mailbox]['d'] = array_merge($this->_slicemap[$mailbox]['d'], $data); 263 $this->_toUpdate($mailbox, 'slicemap', true); 264 } 265 266 /** 267 */ 268 public function deleteMsgs($mailbox, $uids) 269 { 270 if (empty($uids)) { 271 return; 272 } 273 274 $this->_loadSliceMap($mailbox); 275 276 $slicemap = &$this->_slicemap[$mailbox]; 277 $deleted = array_intersect_key($slicemap['s'], array_flip($uids)); 278 279 if (isset($this->_update[$mailbox])) { 280 $this->_update[$mailbox]['add'] = array_diff( 281 $this->_update[$mailbox]['add'], 282 $uids 283 ); 284 } 285 286 if (empty($deleted)) { 287 return; 288 } 289 290 $this->_loadUids($mailbox, array_keys($deleted)); 291 $d = &$this->_data[$mailbox]; 292 293 foreach (array_keys($deleted) as $id) { 294 unset($d[$id], $slicemap['s'][$id]); 295 } 296 297 foreach (array_unique($deleted) as $slice) { 298 /* Get rid of slice if less than 10% of capacity. */ 299 if (($slice != $slicemap['i']) && 300 ($slice_uids = array_keys($slicemap['s'], $slice)) && 301 ($this->_params['slicesize'] * 0.1) > count($slice_uids)) { 302 $this->_toUpdate($mailbox, 'add', $slice_uids); 303 $this->_cache->expire($this->_getCid($mailbox, $slice)); 304 foreach ($slice_uids as $val) { 305 unset($slicemap['s'][$val]); 306 } 307 } else { 308 $this->_toUpdate($mailbox, 'slice', array($slice)); 309 } 310 } 311 } 312 313 /** 314 */ 315 public function deleteMailbox($mailbox) 316 { 317 $this->_loadSliceMap($mailbox); 318 $this->_deleteMailbox($mailbox); 319 } 320 321 /** 322 */ 323 public function clear($lifetime) 324 { 325 $this->_cache->clear(); 326 $this->_data = $this->_loaded = $this->_slicemap = $this->_update = array(); 327 } 328 329 /** 330 * Create the unique ID used to store the data in the cache. 331 * 332 * @param string $mailbox The mailbox to cache. 333 * @param string $slice The cache slice. 334 * 335 * @return string The cache ID. 336 */ 337 protected function _getCid($mailbox, $slice) 338 { 339 return implode('|', array( 340 'horde_imap_client', 341 $this->_params['username'], 342 $mailbox, 343 $this->_params['hostspec'], 344 $this->_params['port'], 345 $slice, 346 self::VERSION 347 )); 348 } 349 350 /** 351 * Delete a mailbox from the cache. 352 * 353 * @param string $mbox The mailbox to delete. 354 */ 355 protected function _deleteMailbox($mbox) 356 { 357 foreach (array_merge(array_keys(array_flip($this->_slicemap[$mbox]['s'])), array('slicemap')) as $slice) { 358 $cid = $this->_getCid($mbox, $slice); 359 $this->_cache->expire($cid); 360 unset($this->_loaded[$cid]); 361 } 362 363 unset( 364 $this->_data[$mbox], 365 $this->_slicemap[$mbox], 366 $this->_update[$mbox] 367 ); 368 } 369 370 /** 371 * Load UIDs by regenerating from the cache. 372 * 373 * @param string $mailbox The mailbox to load. 374 * @param array $uids The UIDs to load. 375 * @param integer $uidvalid The IMAP uidvalidity value of the mailbox. 376 */ 377 protected function _loadUids($mailbox, $uids, $uidvalid = null) 378 { 379 if (!isset($this->_data[$mailbox])) { 380 $this->_data[$mailbox] = array(); 381 } 382 383 $this->_loadSliceMap($mailbox, $uidvalid); 384 385 if (!empty($uids)) { 386 foreach (array_unique(array_intersect_key($this->_slicemap[$mailbox]['s'], array_flip($uids))) as $slice) { 387 $this->_loadSlice($mailbox, $slice); 388 } 389 } 390 } 391 392 /** 393 * Load UIDs from a cache slice. 394 * 395 * @param string $mailbox The mailbox to load. 396 * @param integer $slice The slice to load. 397 */ 398 protected function _loadSlice($mailbox, $slice) 399 { 400 $cache_id = $this->_getCid($mailbox, $slice); 401 402 if (!empty($this->_loaded[$cache_id])) { 403 return; 404 } 405 406 if (($data = $this->_cache->get($cache_id, 0)) !== false) { 407 try { 408 $data = @unserialize($data); 409 } catch (Exception $e) {} 410 } 411 412 if (($data !== false) && is_array($data)) { 413 $this->_data[$mailbox] += $data; 414 $this->_loaded[$cache_id] = true; 415 } else { 416 $ptr = &$this->_slicemap[$mailbox]; 417 418 // Slice data is corrupt; remove from slicemap. 419 foreach (array_keys($ptr['s'], $slice) as $val) { 420 unset($ptr['s'][$val]); 421 } 422 423 if ($slice == $ptr['i']) { 424 $ptr['c'] = 0; 425 } 426 } 427 } 428 429 /** 430 * Load the slicemap for a given mailbox. The slicemap contains 431 * the uidvalidity information, the UIDs->slice lookup table, and any 432 * metadata that needs to be saved for the mailbox. 433 * 434 * @param string $mailbox The mailbox. 435 * @param integer $uidvalid The IMAP uidvalidity value of the mailbox. 436 */ 437 protected function _loadSliceMap($mailbox, $uidvalid = null) 438 { 439 if (!isset($this->_slicemap[$mailbox]) && 440 (($data = $this->_cache->get($this->_getCid($mailbox, 'slicemap'), 0)) !== false)) { 441 try { 442 if (($slice = @unserialize($data)) && 443 is_array($slice)) { 444 $this->_slicemap[$mailbox] = $slice; 445 } 446 } catch (Exception $e) {} 447 } 448 449 if (isset($this->_slicemap[$mailbox])) { 450 $ptr = &$this->_slicemap[$mailbox]; 451 if (is_null($ptr['d']['uidvalid'])) { 452 $ptr['d']['uidvalid'] = $uidvalid; 453 return; 454 } elseif (!is_null($uidvalid) && 455 ($ptr['d']['uidvalid'] != $uidvalid)) { 456 $this->_deleteMailbox($mailbox); 457 } else { 458 return; 459 } 460 } 461 462 $this->_slicemap[$mailbox] = array( 463 // Tracking count for purposes of determining slices 464 'c' => 0, 465 // Metadata storage 466 // By default includes UIDVALIDITY of mailbox. 467 'd' => array('uidvalid' => $uidvalid), 468 // The ID of the last slice. 469 'i' => 0, 470 // The slice list. 471 's' => array() 472 ); 473 } 474 475 /** 476 * Add update entry for a mailbox. 477 * 478 * @param string $mailbox The mailbox. 479 * @param string $type 'add', 'slice', or 'slicemap'. 480 * @param mixed $data The data to update. 481 */ 482 protected function _toUpdate($mailbox, $type, $data) 483 { 484 if (!isset($this->_update[$mailbox])) { 485 $this->_update[$mailbox] = array( 486 'add' => array(), 487 'slice' => array() 488 ); 489 } 490 491 $this->_update[$mailbox][$type] = ($type == 'slicemap') 492 ? $data 493 : array_merge($this->_update[$mailbox][$type], $data); 494 } 495 496 /* Serializable methods. */ 497 498 /** 499 */ 500 public function serialize() 501 { 502 $this->save(); 503 return parent::serialize(); 504 } 505 506 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body