PageRenderTime 51ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/horde/framework/Horde/Imap/Client/Cache/Backend/Cache.php

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