Cache::save()   C
last analyzed

Complexity

Conditions 11
Paths 13

Size

Total Lines 46
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 46
rs 5.2653
c 0
b 0
f 0
cc 11
eloc 28
nc 13
nop 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * @author Lukas Reschke <[email protected]>
5
 * @author Thomas Müller <[email protected]>
6
 *
7
 * Mail
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
The class Horde_Imap_Client_Exception does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
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