These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | /** |
||
4 | * @author Lukas Reschke <[email protected]> |
||
5 | * @author Thomas Müller <[email protected]> |
||
6 | * |
||
7 | |||
8 | * |
||
9 | * This code is free software: you can redistribute it and/or modify |
||
10 | * it under the terms of the GNU Affero General Public License, version 3, |
||
11 | * as published by the Free Software Foundation. |
||
12 | * |
||
13 | * This program is distributed in the hope that it will be useful, |
||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
16 | * GNU Affero General Public License for more details. |
||
17 | * |
||
18 | * You should have received a copy of the GNU Affero General Public License, version 3, |
||
19 | * along with this program. If not, see <http://www.gnu.org/licenses/> |
||
20 | * |
||
21 | */ |
||
22 | |||
23 | namespace OCA\Mail\Cache; |
||
24 | |||
25 | use Horde_Imap_Client_Cache_Backend; |
||
26 | use Horde_Imap_Client_Exception; |
||
27 | use InvalidArgumentException; |
||
28 | |||
29 | /** |
||
30 | * Class UserCache |
||
31 | * |
||
32 | * This class is inspired by Horde_Imap_Client_Cache_Backend_Cache of the Horde Project |
||
33 | * |
||
34 | * @package OCA\Mail\Cache |
||
35 | */ |
||
36 | class Cache extends Horde_Imap_Client_Cache_Backend |
||
37 | { |
||
38 | /** Cache structure version. */ |
||
39 | const VERSION = 3; |
||
40 | |||
41 | /** |
||
42 | * The cache object. |
||
43 | * |
||
44 | * @var \OCP\ICache |
||
45 | */ |
||
46 | protected $_cache; |
||
47 | |||
48 | /** |
||
49 | * The working data for the current pageload. All changes take place to |
||
50 | * this data. |
||
51 | * |
||
52 | * @var array |
||
53 | */ |
||
54 | protected $_data = array(); |
||
55 | |||
56 | /** |
||
57 | * The list of cache slices loaded. |
||
58 | * |
||
59 | * @var array |
||
60 | */ |
||
61 | protected $_loaded = array(); |
||
62 | |||
63 | /** |
||
64 | * The mapping of UIDs to slices. |
||
65 | * |
||
66 | * @var array |
||
67 | */ |
||
68 | protected $_slicemap = array(); |
||
69 | |||
70 | /** |
||
71 | * The list of items to update: |
||
72 | * - add: (array) List of IDs that were added. |
||
73 | * - slice: (array) List of slices that were modified. |
||
74 | * - slicemap: (boolean) Was slicemap info changed? |
||
75 | * |
||
76 | * @var array |
||
77 | */ |
||
78 | protected $_update = array(); |
||
79 | |||
80 | /** |
||
81 | * Constructor. |
||
82 | * |
||
83 | * @param array $params Configuration parameters: |
||
84 | */ |
||
85 | public function __construct(array $params = array()) |
||
86 | { |
||
87 | // Default parameters. |
||
88 | $params = array_merge(array( |
||
89 | 'lifetime' => 604800, |
||
90 | 'slicesize' => 50 |
||
91 | ), array_filter($params)); |
||
92 | |||
93 | if (!isset($params['cacheob'])) { |
||
94 | throw new InvalidArgumentException('Missing cacheob parameter.'); |
||
95 | } |
||
96 | |||
97 | foreach (array('lifetime', 'slicesize') as $val) { |
||
98 | $params[$val] = intval($params[$val]); |
||
99 | } |
||
100 | |||
101 | parent::__construct($params); |
||
102 | } |
||
103 | |||
104 | /** |
||
105 | * Initialization tasks. |
||
106 | */ |
||
107 | protected function _initOb() |
||
108 | { |
||
109 | $this->_cache = $this->_params['cacheob']; |
||
110 | register_shutdown_function(array($this, 'save')); |
||
111 | } |
||
112 | |||
113 | /** |
||
114 | * Updates the cache. |
||
115 | */ |
||
116 | public function save() |
||
117 | { |
||
118 | $lifetime = $this->_params['lifetime']; |
||
119 | |||
120 | foreach ($this->_update as $mbox => $val) { |
||
121 | $s = &$this->_slicemap[$mbox]; |
||
122 | |||
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 | } |
||
159 | |||
160 | $this->_update = array(); |
||
161 | } |
||
162 | |||
163 | /** {@inheritDoc} */ |
||
164 | public function get($mailbox, $uids, $fields, $uidvalid) |
||
165 | { |
||
166 | $ret = array(); |
||
167 | $this->_loadUids($mailbox, $uids, $uidvalid); |
||
168 | |||
169 | if (empty($this->_data[$mailbox])) { |
||
170 | return $ret; |
||
171 | } |
||
172 | |||
173 | if (!empty($fields)) { |
||
174 | $fields = array_flip($fields); |
||
175 | } |
||
176 | $ptr = &$this->_data[$mailbox]; |
||
177 | |||
178 | foreach (array_intersect($uids, array_keys($ptr)) as $val) { |
||
179 | if (is_string($ptr[$val])) { |
||
180 | $ptr[$val] = @unserialize($ptr[$val]); |
||
181 | } |
||
182 | |||
183 | $ret[$val] = (empty($fields) || empty($ptr[$val])) |
||
184 | ? $ptr[$val] |
||
185 | : array_intersect_key($ptr[$val], $fields); |
||
186 | } |
||
187 | |||
188 | return $ret; |
||
189 | } |
||
190 | |||
191 | /** {@inheritDoc} */ |
||
192 | public function getCachedUids($mailbox, $uidvalid) |
||
193 | { |
||
194 | $this->_loadSliceMap($mailbox, $uidvalid); |
||
195 | return array_unique(array_merge( |
||
196 | array_keys($this->_slicemap[$mailbox]['s']), |
||
197 | (isset($this->_update[$mailbox]) ? $this->_update[$mailbox]['add'] : array()) |
||
198 | )); |
||
199 | } |
||
200 | |||
201 | /** {@inheritDoc} */ |
||
202 | public function set($mailbox, $data, $uidvalid) |
||
203 | { |
||
204 | $update = array_keys($data); |
||
205 | |||
206 | try { |
||
207 | $this->_loadUids($mailbox, $update, $uidvalid); |
||
208 | } catch (Horde_Imap_Client_Exception $e) { |
||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
209 | // Ignore invalidity - just start building the new cache |
||
210 | } |
||
211 | |||
212 | $d = &$this->_data[$mailbox]; |
||
213 | $s = &$this->_slicemap[$mailbox]['s']; |
||
214 | $add = $updated = array(); |
||
215 | |||
216 | foreach ($data as $k => $v) { |
||
217 | if (isset($d[$k])) { |
||
218 | if (is_string($d[$k])) { |
||
219 | $d[$k] = @unserialize($d[$k]); |
||
220 | } |
||
221 | $d[$k] = is_array($d[$k]) |
||
222 | ? array_merge($d[$k], $v) |
||
223 | : $v; |
||
224 | if (isset($s[$k])) { |
||
225 | $updated[$s[$k]] = true; |
||
226 | } |
||
227 | } else { |
||
228 | $d[$k] = $v; |
||
229 | $add[] = $k; |
||
230 | } |
||
231 | } |
||
232 | |||
233 | $this->_toUpdate($mailbox, 'add', $add); |
||
234 | $this->_toUpdate($mailbox, 'slice', array_keys($updated)); |
||
235 | } |
||
236 | |||
237 | /** {@inheritDoc} */ |
||
238 | public function getMetaData($mailbox, $uidvalid, $entries) |
||
239 | { |
||
240 | $this->_loadSliceMap($mailbox, $uidvalid); |
||
241 | |||
242 | return empty($entries) |
||
243 | ? $this->_slicemap[$mailbox]['d'] |
||
244 | : array_intersect_key($this->_slicemap[$mailbox]['d'], array_flip($entries)); |
||
245 | } |
||
246 | |||
247 | /** {@inheritDoc} */ |
||
248 | public function setMetaData($mailbox, $data) |
||
249 | { |
||
250 | $this->_loadSliceMap($mailbox, isset($data['uidvalid']) ? $data['uidvalid'] : null); |
||
251 | $this->_slicemap[$mailbox]['d'] = array_merge($this->_slicemap[$mailbox]['d'], $data); |
||
252 | $this->_toUpdate($mailbox, 'slicemap', true); |
||
253 | } |
||
254 | |||
255 | /** {@inheritDoc} */ |
||
256 | public function deleteMsgs($mailbox, $uids) |
||
257 | { |
||
258 | $this->_loadSliceMap($mailbox); |
||
259 | |||
260 | $slicemap = &$this->_slicemap[$mailbox]; |
||
261 | $deleted = array_intersect_key($slicemap['s'], array_flip($uids)); |
||
262 | |||
263 | if (isset($this->_update[$mailbox])) { |
||
264 | $this->_update[$mailbox]['add'] = array_diff( |
||
265 | $this->_update[$mailbox]['add'], |
||
266 | $uids |
||
267 | ); |
||
268 | } |
||
269 | |||
270 | if (empty($deleted)) { |
||
271 | return; |
||
272 | } |
||
273 | |||
274 | $this->_loadUids($mailbox, array_keys($deleted)); |
||
275 | $d = &$this->_data[$mailbox]; |
||
276 | |||
277 | foreach (array_keys($deleted) as $id) { |
||
278 | unset($d[$id], $slicemap['s'][$id]); |
||
279 | } |
||
280 | |||
281 | foreach (array_unique($deleted) as $slice) { |
||
282 | /* Get rid of slice if less than 10% of capacity. */ |
||
283 | if (($slice != $slicemap['i']) && |
||
284 | ($slice_uids = array_keys($slicemap['s'], $slice)) && |
||
285 | ($this->_params['slicesize'] * 0.1) > count($slice_uids)) { |
||
286 | $this->_toUpdate($mailbox, 'add', $slice_uids); |
||
287 | $this->_cache->remove($this->_getCid($mailbox, $slice)); |
||
288 | foreach ($slice_uids as $val) { |
||
289 | unset($slicemap['s'][$val]); |
||
290 | } |
||
291 | } else { |
||
292 | $this->_toUpdate($mailbox, 'slice', array($slice)); |
||
293 | } |
||
294 | } |
||
295 | } |
||
296 | |||
297 | /** {@inheritDoc} */ |
||
298 | public function deleteMailbox($mailbox) |
||
299 | { |
||
300 | $this->_loadSliceMap($mailbox); |
||
301 | $this->_deleteMailbox($mailbox); |
||
302 | } |
||
303 | |||
304 | /** {@inheritDoc} */ |
||
305 | public function clear($lifetime) |
||
306 | { |
||
307 | $this->_cache->clear(); |
||
308 | $this->_data = $this->_loaded = $this->_slicemap = $this->_update = array(); |
||
309 | } |
||
310 | |||
311 | /** |
||
312 | * Create the unique ID used to store the data in the cache. |
||
313 | * |
||
314 | * @param string $mailbox The mailbox to cache. |
||
315 | * @param string $slice The cache slice. |
||
316 | * |
||
317 | * @return string The cache ID. |
||
318 | */ |
||
319 | protected function _getCid($mailbox, $slice) |
||
320 | { |
||
321 | return implode('|', array( |
||
322 | 'horde_imap_client', |
||
323 | $this->_params['username'], |
||
324 | $mailbox, |
||
325 | $this->_params['hostspec'], |
||
326 | $this->_params['port'], |
||
327 | $slice, |
||
328 | self::VERSION |
||
329 | )); |
||
330 | } |
||
331 | |||
332 | /** |
||
333 | * Delete a mailbox from the cache. |
||
334 | * |
||
335 | * @param string $mbox The mailbox to delete. |
||
336 | */ |
||
337 | protected function _deleteMailbox($mbox) |
||
338 | { |
||
339 | foreach (array_merge(array_keys(array_flip($this->_slicemap[$mbox]['s'])), array('slicemap')) as $slice) { |
||
340 | $cid = $this->_getCid($mbox, $slice); |
||
341 | $this->_cache->remove($cid); |
||
342 | unset($this->_loaded[$cid]); |
||
343 | } |
||
344 | |||
345 | unset( |
||
346 | $this->_data[$mbox], |
||
347 | $this->_slicemap[$mbox], |
||
348 | $this->_update[$mbox] |
||
349 | ); |
||
350 | } |
||
351 | |||
352 | /** |
||
353 | * Load UIDs by regenerating from the cache. |
||
354 | * |
||
355 | * @param string $mailbox The mailbox to load. |
||
356 | * @param array $uids The UIDs to load. |
||
357 | * @param integer $uidvalid The IMAP uidvalidity value of the mailbox. |
||
358 | */ |
||
359 | protected function _loadUids($mailbox, $uids, $uidvalid = null) |
||
360 | { |
||
361 | if (!isset($this->_data[$mailbox])) { |
||
362 | $this->_data[$mailbox] = array(); |
||
363 | } |
||
364 | |||
365 | $this->_loadSliceMap($mailbox, $uidvalid); |
||
366 | |||
367 | if (!empty($uids)) { |
||
368 | foreach (array_unique(array_intersect_key($this->_slicemap[$mailbox]['s'], array_flip($uids))) as $slice) { |
||
369 | $this->_loadSlice($mailbox, $slice); |
||
370 | } |
||
371 | } |
||
372 | } |
||
373 | |||
374 | /** |
||
375 | * Load UIDs from a cache slice. |
||
376 | * |
||
377 | * @param string $mailbox The mailbox to load. |
||
378 | * @param integer $slice The slice to load. |
||
379 | */ |
||
380 | protected function _loadSlice($mailbox, $slice) |
||
381 | { |
||
382 | $cache_id = $this->_getCid($mailbox, $slice); |
||
383 | |||
384 | if (!empty($this->_loaded[$cache_id])) { |
||
385 | return; |
||
386 | } |
||
387 | |||
388 | if ((($data = $this->_cache->get($cache_id, 0)) !== false) && |
||
389 | ($data = @unserialize($data)) && |
||
390 | is_array($data)) { |
||
391 | $this->_data[$mailbox] += $data; |
||
392 | $this->_loaded[$cache_id] = true; |
||
393 | } else { |
||
394 | $ptr = &$this->_slicemap[$mailbox]; |
||
395 | |||
396 | // Slice data is corrupt; remove from slicemap. |
||
397 | foreach (array_keys($ptr['s'], $slice) as $val) { |
||
398 | unset($ptr['s'][$val]); |
||
399 | } |
||
400 | |||
401 | if ($slice == $ptr['i']) { |
||
402 | $ptr['c'] = 0; |
||
403 | } |
||
404 | } |
||
405 | } |
||
406 | |||
407 | /** |
||
408 | * Load the slicemap for a given mailbox. The slicemap contains |
||
409 | * the uidvalidity information, the UIDs->slice lookup table, and any |
||
410 | * metadata that needs to be saved for the mailbox. |
||
411 | * |
||
412 | * @param string $mailbox The mailbox. |
||
413 | * @param integer $uidvalid The IMAP uidvalidity value of the mailbox. |
||
414 | */ |
||
415 | protected function _loadSliceMap($mailbox, $uidvalid = null) |
||
416 | { |
||
417 | if (!isset($this->_slicemap[$mailbox]) && |
||
418 | (($data = $this->_cache->get($this->_getCid($mailbox, 'slicemap'), 0)) !== false) && |
||
419 | ($slice = @unserialize($data)) && |
||
420 | is_array($slice)) { |
||
421 | $this->_slicemap[$mailbox] = $slice; |
||
422 | } |
||
423 | |||
424 | if (isset($this->_slicemap[$mailbox])) { |
||
425 | $ptr = &$this->_slicemap[$mailbox]; |
||
426 | if (is_null($ptr['d']['uidvalid'])) { |
||
427 | $ptr['d']['uidvalid'] = $uidvalid; |
||
428 | return; |
||
429 | } elseif (!is_null($uidvalid) && |
||
430 | ($ptr['d']['uidvalid'] != $uidvalid)) { |
||
431 | $this->_deleteMailbox($mailbox); |
||
432 | } else { |
||
433 | return; |
||
434 | } |
||
435 | } |
||
436 | |||
437 | $this->_slicemap[$mailbox] = array( |
||
438 | // Tracking count for purposes of determining slices |
||
439 | 'c' => 0, |
||
440 | // Metadata storage |
||
441 | // By default includes UIDVALIDITY of mailbox. |
||
442 | 'd' => array('uidvalid' => $uidvalid), |
||
443 | // The ID of the last slice. |
||
444 | 'i' => 0, |
||
445 | // The slice list. |
||
446 | 's' => array() |
||
447 | ); |
||
448 | } |
||
449 | |||
450 | /** |
||
451 | * Add update entry for a mailbox. |
||
452 | * |
||
453 | * @param string $mailbox The mailbox. |
||
454 | * @param string $type 'add', 'slice', or 'slicemap'. |
||
455 | * @param mixed $data The data to update. |
||
456 | */ |
||
457 | protected function _toUpdate($mailbox, $type, $data) |
||
458 | { |
||
459 | if (!isset($this->_update[$mailbox])) { |
||
460 | $this->_update[$mailbox] = array( |
||
461 | 'add' => array(), |
||
462 | 'slice' => array() |
||
463 | ); |
||
464 | } |
||
465 | |||
466 | $this->_update[$mailbox][$type] = ($type == 'slicemap') |
||
467 | ? $data |
||
468 | : array_merge($this->_update[$mailbox][$type], $data); |
||
469 | } |
||
470 | |||
471 | /* Serializable methods. */ |
||
472 | |||
473 | /** |
||
474 | */ |
||
475 | public function serialize() |
||
476 | { |
||
477 | $this->save(); |
||
478 | return parent::serialize(); |
||
479 | } |
||
480 | |||
481 | } |
||
482 |