/lib/horde/framework/Horde/Imap/Client/Cache/Backend/Cache.php
PHP | 506 lines | 301 code | 64 blank | 141 comment | 37 complexity | 472f938beba55ea206a759aa396398fd MD5 | raw file
Possible License(s): MIT, AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, Apache-2.0, LGPL-2.1, BSD-3-Clause
- <?php
- /**
- * Copyright 2005-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file LICENSE for license information (LGPL). If you
- * did not receive this file, see http://www.horde.org/licenses/lgpl21.
- *
- * @category Horde
- * @copyright 2005-2017 Horde LLC
- * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
- * @package Imap_Client
- */
- /**
- * A Horde_Cache implementation for caching IMAP/POP data.
- * Requires the Horde_Cache package.
- *
- * @author Michael Slusarz <slusarz@horde.org>
- * @category Horde
- * @copyright 2005-2017 Horde LLC
- * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
- * @package Imap_Client
- */
- class Horde_Imap_Client_Cache_Backend_Cache
- extends Horde_Imap_Client_Cache_Backend
- {
- /** Cache structure version. */
- const VERSION = 3;
- /**
- * The cache object.
- *
- * @var Horde_Cache
- */
- protected $_cache;
- /**
- * The working data for the current pageload. All changes take place to
- * this data.
- *
- * @var array
- */
- protected $_data = array();
- /**
- * The list of cache slices loaded.
- *
- * @var array
- */
- protected $_loaded = array();
- /**
- * The mapping of UIDs to slices.
- *
- * @var array
- */
- protected $_slicemap = array();
- /**
- * The list of items to update:
- * - add: (array) List of IDs that were added.
- * - slice: (array) List of slices that were modified.
- * - slicemap: (boolean) Was slicemap info changed?
- *
- * @var array
- */
- protected $_update = array();
- /**
- * Constructor.
- *
- * @param array $params Configuration parameters:
- * <pre>
- * - REQUIRED Parameters:
- * - cacheob: (Horde_Cache) The cache object to use.
- *
- * - Optional Parameters:
- * - lifetime: (integer) The lifetime of the cache data (in seconds).
- * DEFAULT: 1 week (604800 seconds)
- * - slicesize: (integer) The slicesize to use.
- * DEFAULT: 50
- * </pre>
- */
- public function __construct(array $params = array())
- {
- // Default parameters.
- $params = array_merge(array(
- 'lifetime' => 604800,
- 'slicesize' => 50
- ), array_filter($params));
- if (!isset($params['cacheob'])) {
- throw new InvalidArgumentException('Missing cacheob parameter.');
- }
- foreach (array('lifetime', 'slicesize') as $val) {
- $params[$val] = intval($params[$val]);
- }
- parent::__construct($params);
- }
- /**
- * Initialization tasks.
- */
- protected function _initOb()
- {
- $this->_cache = $this->_params['cacheob'];
- register_shutdown_function(array($this, 'save'));
- }
- /**
- * Updates the cache.
- */
- public function save()
- {
- $lifetime = $this->_params['lifetime'];
- foreach ($this->_update as $mbox => $val) {
- $s = &$this->_slicemap[$mbox];
- try {
- if (!empty($val['add'])) {
- if ($s['c'] <= $this->_params['slicesize']) {
- $val['slice'][] = $s['i'];
- $this->_loadSlice($mbox, $s['i']);
- }
- $val['slicemap'] = true;
- foreach (array_keys(array_flip($val['add'])) as $uid) {
- if ($s['c']++ > $this->_params['slicesize']) {
- $s['c'] = 0;
- $val['slice'][] = ++$s['i'];
- $this->_loadSlice($mbox, $s['i']);
- }
- $s['s'][$uid] = $s['i'];
- }
- }
- if (!empty($val['slice'])) {
- $d = &$this->_data[$mbox];
- $val['slicemap'] = true;
- foreach (array_keys(array_flip($val['slice'])) as $slice) {
- $data = array();
- foreach (array_keys($s['s'], $slice) as $uid) {
- $data[$uid] = is_array($d[$uid])
- ? serialize($d[$uid])
- : $d[$uid];
- }
- $this->_cache->set($this->_getCid($mbox, $slice), serialize($data), $lifetime);
- }
- }
- if (!empty($val['slicemap'])) {
- $this->_cache->set($this->_getCid($mbox, 'slicemap'), serialize($s), $lifetime);
- }
- } catch (Horde_Exception $e) {
- }
- }
- $this->_update = array();
- }
- /**
- */
- public function get($mailbox, $uids, $fields, $uidvalid)
- {
- $ret = array();
- $this->_loadUids($mailbox, $uids, $uidvalid);
- if (empty($this->_data[$mailbox])) {
- return $ret;
- }
- if (!empty($fields)) {
- $fields = array_flip($fields);
- }
- $ptr = &$this->_data[$mailbox];
- foreach (array_intersect($uids, array_keys($ptr)) as $val) {
- if (is_string($ptr[$val])) {
- try {
- $ptr[$val] = @unserialize($ptr[$val]);
- } catch (Exception $e) {}
- }
- $ret[$val] = (empty($fields) || empty($ptr[$val]))
- ? $ptr[$val]
- : array_intersect_key($ptr[$val], $fields);
- }
- return $ret;
- }
- /**
- */
- public function getCachedUids($mailbox, $uidvalid)
- {
- $this->_loadSliceMap($mailbox, $uidvalid);
- return array_unique(array_merge(
- array_keys($this->_slicemap[$mailbox]['s']),
- (isset($this->_update[$mailbox]) ? $this->_update[$mailbox]['add'] : array())
- ));
- }
- /**
- */
- public function set($mailbox, $data, $uidvalid)
- {
- $update = array_keys($data);
- try {
- $this->_loadUids($mailbox, $update, $uidvalid);
- } catch (Horde_Imap_Client_Exception $e) {
- // Ignore invalidity - just start building the new cache
- }
- $d = &$this->_data[$mailbox];
- $s = &$this->_slicemap[$mailbox]['s'];
- $add = $updated = array();
- foreach ($data as $k => $v) {
- if (isset($d[$k])) {
- if (is_string($d[$k])) {
- try {
- $d[$k] = @unserialize($d[$k]);
- } catch (Exception $e) {}
- }
- $d[$k] = is_array($d[$k])
- ? array_merge($d[$k], $v)
- : $v;
- if (isset($s[$k])) {
- $updated[$s[$k]] = true;
- }
- } else {
- $d[$k] = $v;
- $add[] = $k;
- }
- }
- $this->_toUpdate($mailbox, 'add', $add);
- $this->_toUpdate($mailbox, 'slice', array_keys($updated));
- }
- /**
- */
- public function getMetaData($mailbox, $uidvalid, $entries)
- {
- $this->_loadSliceMap($mailbox, $uidvalid);
- return empty($entries)
- ? $this->_slicemap[$mailbox]['d']
- : array_intersect_key($this->_slicemap[$mailbox]['d'], array_flip($entries));
- }
- /**
- */
- public function setMetaData($mailbox, $data)
- {
- $this->_loadSliceMap($mailbox, isset($data['uidvalid']) ? $data['uidvalid'] : null);
- $this->_slicemap[$mailbox]['d'] = array_merge($this->_slicemap[$mailbox]['d'], $data);
- $this->_toUpdate($mailbox, 'slicemap', true);
- }
- /**
- */
- public function deleteMsgs($mailbox, $uids)
- {
- if (empty($uids)) {
- return;
- }
- $this->_loadSliceMap($mailbox);
- $slicemap = &$this->_slicemap[$mailbox];
- $deleted = array_intersect_key($slicemap['s'], array_flip($uids));
- if (isset($this->_update[$mailbox])) {
- $this->_update[$mailbox]['add'] = array_diff(
- $this->_update[$mailbox]['add'],
- $uids
- );
- }
- if (empty($deleted)) {
- return;
- }
- $this->_loadUids($mailbox, array_keys($deleted));
- $d = &$this->_data[$mailbox];
- foreach (array_keys($deleted) as $id) {
- unset($d[$id], $slicemap['s'][$id]);
- }
- foreach (array_unique($deleted) as $slice) {
- /* Get rid of slice if less than 10% of capacity. */
- if (($slice != $slicemap['i']) &&
- ($slice_uids = array_keys($slicemap['s'], $slice)) &&
- ($this->_params['slicesize'] * 0.1) > count($slice_uids)) {
- $this->_toUpdate($mailbox, 'add', $slice_uids);
- $this->_cache->expire($this->_getCid($mailbox, $slice));
- foreach ($slice_uids as $val) {
- unset($slicemap['s'][$val]);
- }
- } else {
- $this->_toUpdate($mailbox, 'slice', array($slice));
- }
- }
- }
- /**
- */
- public function deleteMailbox($mailbox)
- {
- $this->_loadSliceMap($mailbox);
- $this->_deleteMailbox($mailbox);
- }
- /**
- */
- public function clear($lifetime)
- {
- $this->_cache->clear();
- $this->_data = $this->_loaded = $this->_slicemap = $this->_update = array();
- }
- /**
- * Create the unique ID used to store the data in the cache.
- *
- * @param string $mailbox The mailbox to cache.
- * @param string $slice The cache slice.
- *
- * @return string The cache ID.
- */
- protected function _getCid($mailbox, $slice)
- {
- return implode('|', array(
- 'horde_imap_client',
- $this->_params['username'],
- $mailbox,
- $this->_params['hostspec'],
- $this->_params['port'],
- $slice,
- self::VERSION
- ));
- }
- /**
- * Delete a mailbox from the cache.
- *
- * @param string $mbox The mailbox to delete.
- */
- protected function _deleteMailbox($mbox)
- {
- foreach (array_merge(array_keys(array_flip($this->_slicemap[$mbox]['s'])), array('slicemap')) as $slice) {
- $cid = $this->_getCid($mbox, $slice);
- $this->_cache->expire($cid);
- unset($this->_loaded[$cid]);
- }
- unset(
- $this->_data[$mbox],
- $this->_slicemap[$mbox],
- $this->_update[$mbox]
- );
- }
- /**
- * Load UIDs by regenerating from the cache.
- *
- * @param string $mailbox The mailbox to load.
- * @param array $uids The UIDs to load.
- * @param integer $uidvalid The IMAP uidvalidity value of the mailbox.
- */
- protected function _loadUids($mailbox, $uids, $uidvalid = null)
- {
- if (!isset($this->_data[$mailbox])) {
- $this->_data[$mailbox] = array();
- }
- $this->_loadSliceMap($mailbox, $uidvalid);
- if (!empty($uids)) {
- foreach (array_unique(array_intersect_key($this->_slicemap[$mailbox]['s'], array_flip($uids))) as $slice) {
- $this->_loadSlice($mailbox, $slice);
- }
- }
- }
- /**
- * Load UIDs from a cache slice.
- *
- * @param string $mailbox The mailbox to load.
- * @param integer $slice The slice to load.
- */
- protected function _loadSlice($mailbox, $slice)
- {
- $cache_id = $this->_getCid($mailbox, $slice);
- if (!empty($this->_loaded[$cache_id])) {
- return;
- }
- if (($data = $this->_cache->get($cache_id, 0)) !== false) {
- try {
- $data = @unserialize($data);
- } catch (Exception $e) {}
- }
- if (($data !== false) && is_array($data)) {
- $this->_data[$mailbox] += $data;
- $this->_loaded[$cache_id] = true;
- } else {
- $ptr = &$this->_slicemap[$mailbox];
- // Slice data is corrupt; remove from slicemap.
- foreach (array_keys($ptr['s'], $slice) as $val) {
- unset($ptr['s'][$val]);
- }
- if ($slice == $ptr['i']) {
- $ptr['c'] = 0;
- }
- }
- }
- /**
- * Load the slicemap for a given mailbox. The slicemap contains
- * the uidvalidity information, the UIDs->slice lookup table, and any
- * metadata that needs to be saved for the mailbox.
- *
- * @param string $mailbox The mailbox.
- * @param integer $uidvalid The IMAP uidvalidity value of the mailbox.
- */
- protected function _loadSliceMap($mailbox, $uidvalid = null)
- {
- if (!isset($this->_slicemap[$mailbox]) &&
- (($data = $this->_cache->get($this->_getCid($mailbox, 'slicemap'), 0)) !== false)) {
- try {
- if (($slice = @unserialize($data)) &&
- is_array($slice)) {
- $this->_slicemap[$mailbox] = $slice;
- }
- } catch (Exception $e) {}
- }
- if (isset($this->_slicemap[$mailbox])) {
- $ptr = &$this->_slicemap[$mailbox];
- if (is_null($ptr['d']['uidvalid'])) {
- $ptr['d']['uidvalid'] = $uidvalid;
- return;
- } elseif (!is_null($uidvalid) &&
- ($ptr['d']['uidvalid'] != $uidvalid)) {
- $this->_deleteMailbox($mailbox);
- } else {
- return;
- }
- }
- $this->_slicemap[$mailbox] = array(
- // Tracking count for purposes of determining slices
- 'c' => 0,
- // Metadata storage
- // By default includes UIDVALIDITY of mailbox.
- 'd' => array('uidvalid' => $uidvalid),
- // The ID of the last slice.
- 'i' => 0,
- // The slice list.
- 's' => array()
- );
- }
- /**
- * Add update entry for a mailbox.
- *
- * @param string $mailbox The mailbox.
- * @param string $type 'add', 'slice', or 'slicemap'.
- * @param mixed $data The data to update.
- */
- protected function _toUpdate($mailbox, $type, $data)
- {
- if (!isset($this->_update[$mailbox])) {
- $this->_update[$mailbox] = array(
- 'add' => array(),
- 'slice' => array()
- );
- }
- $this->_update[$mailbox][$type] = ($type == 'slicemap')
- ? $data
- : array_merge($this->_update[$mailbox][$type], $data);
- }
- /* Serializable methods. */
- /**
- */
- public function serialize()
- {
- $this->save();
- return parent::serialize();
- }
- }