Passed
Push — master ( 7b0e11...1d7207 )
by Morris
28:08 queued 15s
created

runForBackend()   F

Complexity

Conditions 14
Paths 471

Size

Total Lines 79
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 49
nc 471
nop 5
dl 0
loc 79
rs 2.8347
c 0
b 0
f 0

How to fix   Long Method    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
 * @copyright 2019, Georg Ehrke <[email protected]>
4
 *
5
 * @author Georg Ehrke <[email protected]>
6
 *
7
 * @license GNU AGPL version 3 or any later version
8
 *
9
 * This program is free software: you can redistribute it and/or modify
10
 * it under the terms of the GNU Affero General Public License as
11
 * published by the Free Software Foundation, either version 3 of the
12
 * License, or (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public License
20
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 *
22
 */
23
24
namespace OCA\DAV\BackgroundJob;
25
26
use OC\BackgroundJob\TimedJob;
27
use OCA\DAV\CalDAV\CalDavBackend;
28
use OCP\Calendar\BackendTemporarilyUnavailableException;
29
use OCP\Calendar\IMetadataProvider;
30
use OCP\Calendar\Resource\IBackend as IResourceBackend;
31
use OCP\Calendar\Resource\IManager as IResourceManager;
32
use OCP\Calendar\Resource\IResource;
33
use OCP\Calendar\Room\IManager as IRoomManager;
34
use OCP\Calendar\Room\IRoom;
35
use OCP\IDBConnection;
36
37
class UpdateCalendarResourcesRoomsBackgroundJob extends TimedJob {
38
39
	/** @var IResourceManager */
40
	private $resourceManager;
41
42
	/** @var IRoomManager */
43
	private $roomManager;
44
45
	/** @var IDBConnection */
46
	private $dbConnection;
47
48
	/** @var CalDavBackend */
49
	private $calDavBackend;
50
51
	/**
52
	 * UpdateCalendarResourcesRoomsBackgroundJob constructor.
53
	 *
54
	 * @param IResourceManager $resourceManager
55
	 * @param IRoomManager $roomManager
56
	 * @param IDBConnection $dbConnection
57
	 * @param CalDavBackend $calDavBackend
58
	 */
59
	public function __construct(IResourceManager $resourceManager,
60
								IRoomManager $roomManager,
61
								IDBConnection $dbConnection,
62
								CalDavBackend $calDavBackend) {
63
		$this->resourceManager = $resourceManager;
64
		$this->roomManager = $roomManager;
65
		$this->dbConnection = $dbConnection;
66
		$this->calDavBackend = $calDavBackend;
67
68
		// run once an hour
69
		$this->setInterval(60 * 60);
70
	}
71
72
	/**
73
	 * @param $argument
74
	 */
75
	public function run($argument):void {
76
		$this->runForBackend(
77
			$this->resourceManager,
78
			'calendar_resources',
79
			'calendar_resources_md',
80
			'resource_id',
81
			'principals/calendar-resources'
82
		);
83
		$this->runForBackend(
84
			$this->roomManager,
85
			'calendar_rooms',
86
			'calendar_rooms_md',
87
			'room_id',
88
			'principals/calendar-rooms'
89
		);
90
	}
91
92
	/**
93
	 * Run background-job for one specific backendManager
94
	 * either ResourceManager or RoomManager
95
	 *
96
	 * @param IResourceManager|IRoomManager $backendManager
97
	 * @param string $dbTable
98
	 * @param string $dbTableMetadata
99
	 * @param string $foreignKey
100
	 * @param string $principalPrefix
101
	 */
102
	private function runForBackend($backendManager,
103
								   string $dbTable,
104
								   string $dbTableMetadata,
105
								   string $foreignKey,
106
								   string $principalPrefix):void {
107
		$backends = $backendManager->getBackends();
108
109
		foreach($backends as $backend) {
110
			$backendId = $backend->getBackendIdentifier();
111
112
			try {
113
				if ($backend instanceof IResourceBackend) {
114
					$list = $backend->listAllResources();
115
				} else {
116
					$list = $backend->listAllRooms();
117
				}
118
			} catch(BackendTemporarilyUnavailableException $ex) {
119
				continue;
120
			}
121
122
			$cachedList = $this->getAllCachedByBackend($dbTable, $backendId);
123
			$newIds = array_diff($list, $cachedList);
124
			$deletedIds = array_diff($cachedList, $list);
125
			$editedIds = array_intersect($list, $cachedList);
126
127
			foreach($newIds as $newId) {
128
				try {
129
					if ($backend instanceof IResourceBackend) {
130
						$resource = $backend->getResource($newId);
131
					} else {
132
						$resource = $backend->getRoom($newId);
133
					}
134
135
					$metadata = [];
136
					if ($resource instanceof IMetadataProvider) {
137
						$metadata = $this->getAllMetadataOfBackend($resource);
138
					}
139
				} catch(BackendTemporarilyUnavailableException $ex) {
140
					continue;
141
				}
142
143
				$id = $this->addToCache($dbTable, $backendId, $resource);
144
				$this->addMetadataToCache($dbTableMetadata, $foreignKey, $id, $metadata);
145
				// we don't create the calendar here, it is created lazily
146
				// when an event is actually scheduled with this resource / room
147
			}
148
149
			foreach($deletedIds as $deletedId) {
150
				$id = $this->getIdForBackendAndResource($dbTable, $backendId, $deletedId);
151
				$this->deleteFromCache($dbTable, $id);
152
				$this->deleteMetadataFromCache($dbTableMetadata, $foreignKey, $id);
153
154
				$principalName = implode('-', [$backendId, $deletedId]);
155
				$this->deleteCalendarDataForResource($principalPrefix, $principalName);
156
			}
157
158
			foreach($editedIds as $editedId) {
159
				$id = $this->getIdForBackendAndResource($dbTable, $backendId, $editedId);
160
161
				try {
162
					if ($backend instanceof IResourceBackend) {
163
						$resource = $backend->getResource($editedId);
164
					} else {
165
						$resource = $backend->getRoom($editedId);
166
					}
167
168
					$metadata = [];
169
					if ($resource instanceof IMetadataProvider) {
170
						$metadata = $this->getAllMetadataOfBackend($resource);
171
					}
172
				} catch(BackendTemporarilyUnavailableException $ex) {
173
					continue;
174
				}
175
176
				$this->updateCache($dbTable, $id, $resource);
177
178
				if ($resource instanceof IMetadataProvider) {
179
					$cachedMetadata = $this->getAllMetadataOfCache($dbTableMetadata, $foreignKey, $id);
180
					$this->updateMetadataCache($dbTableMetadata, $foreignKey, $id, $metadata, $cachedMetadata);
181
				}
182
			}
183
		}
184
	}
185
186
	/**
187
	 * add entry to cache that exists remotely but not yet in cache
188
	 *
189
	 * @param string $table
190
	 * @param string $backendId
191
	 * @param IResource|IRoom $remote
192
	 * @return int Insert id
193
	 */
194
	private function addToCache(string $table,
195
								string $backendId,
196
								$remote):int {
197
		$query = $this->dbConnection->getQueryBuilder();
198
		$query->insert($table)
199
			->values([
200
				'backend_id' => $query->createNamedParameter($backendId),
201
				'resource_id' => $query->createNamedParameter($remote->getId()),
202
				'email' => $query->createNamedParameter($remote->getEMail()),
203
				'displayname' => $query->createNamedParameter($remote->getDisplayName()),
204
				'group_restrictions' => $query->createNamedParameter(
205
					$this->serializeGroupRestrictions(
206
						$remote->getGroupRestrictions()
207
					))
208
			])
209
			->execute();
210
		return $query->getLastInsertId();
211
	}
212
213
	/**
214
	 * @param string $table
215
	 * @param string $foreignKey
216
	 * @param int $foreignId
217
	 * @param array $metadata
218
	 */
219
	private function addMetadataToCache(string $table,
220
										string $foreignKey,
221
										int $foreignId,
222
										array $metadata):void {
223
		foreach($metadata as $key => $value) {
224
			$query = $this->dbConnection->getQueryBuilder();
225
			$query->insert($table)
226
				->values([
227
					$foreignKey => $query->createNamedParameter($foreignId),
228
					'key' => $query->createNamedParameter($key),
229
					'value' => $query->createNamedParameter($value),
230
				])
231
				->execute();
232
		}
233
	}
234
235
	/**
236
	 * delete entry from cache that does not exist anymore remotely
237
	 *
238
	 * @param string $table
239
	 * @param int $id
240
	 */
241
	private function deleteFromCache(string $table,
242
									 int $id):void {
243
		$query = $this->dbConnection->getQueryBuilder();
244
		$query->delete($table)
245
			->where($query->expr()->eq('id', $query->createNamedParameter($id)))
246
			->execute();
247
	}
248
249
	/**
250
	 * @param string $table
251
	 * @param string $foreignKey
252
	 * @param int $id
253
	 */
254
	private function deleteMetadataFromCache(string $table,
255
											 string $foreignKey,
256
											 int $id):void {
257
		$query = $this->dbConnection->getQueryBuilder();
258
		$query->delete($table)
259
			->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
260
			->execute();
261
	}
262
263
	/**
264
	 * update an existing entry in cache
265
	 *
266
	 * @param string $table
267
	 * @param int $id
268
	 * @param IResource|IRoom $remote
269
	 */
270
	private function updateCache(string $table,
271
								 int $id,
272
								 $remote):void {
273
		$query = $this->dbConnection->getQueryBuilder();
274
		$query->update($table)
275
			->set('email', $query->createNamedParameter($remote->getEMail()))
276
			->set('displayname', $query->createNamedParameter($remote->getDisplayName()))
277
			->set('group_restrictions', $query->createNamedParameter(
278
				$this->serializeGroupRestrictions(
279
					$remote->getGroupRestrictions()
280
				)))
281
			->where($query->expr()->eq('id', $query->createNamedParameter($id)))
282
			->execute();
283
	}
284
285
	/**
286
	 * @param string $dbTable
287
	 * @param string $foreignKey
288
	 * @param int $id
289
	 * @param array $metadata
290
	 * @param array $cachedMetadata
291
	 */
292
	private function updateMetadataCache(string $dbTable,
293
										 string $foreignKey,
294
										 int $id,
295
										 array $metadata,
296
										 array $cachedMetadata):void {
297
		$newMetadata = array_diff_key($metadata, $cachedMetadata);
298
		$deletedMetadata = array_diff_key($cachedMetadata, $metadata);
299
300
		foreach ($newMetadata as $key => $value) {
301
			$query = $this->dbConnection->getQueryBuilder();
302
			$query->insert($dbTable)
303
				->values([
304
					$foreignKey => $query->createNamedParameter($id),
305
					'key' => $query->createNamedParameter($key),
306
					'value' => $query->createNamedParameter($value),
307
				])
308
				->execute();
309
		}
310
311
		foreach($deletedMetadata as $key => $value) {
312
			$query = $this->dbConnection->getQueryBuilder();
313
			$query->delete($dbTable)
314
				->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
315
				->andWhere($query->expr()->eq('key', $query->createNamedParameter($key)))
316
				->execute();
317
		}
318
319
		$existingKeys = array_keys(array_intersect_key($metadata, $cachedMetadata));
320
		foreach($existingKeys as $existingKey) {
321
			if ($metadata[$existingKey] !== $cachedMetadata[$existingKey]) {
322
				$query = $this->dbConnection->getQueryBuilder();
323
				$query->update($dbTable)
324
					->set('value', $query->createNamedParameter($metadata[$existingKey]))
325
					->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
326
					->andWhere($query->expr()->eq('key', $query->createNamedParameter($existingKey)))
327
					->execute();
328
			}
329
		}
330
	}
331
332
	/**
333
	 * serialize array of group restrictions to store them in database
334
	 *
335
	 * @param array $groups
336
	 * @return string
337
	 */
338
	private function serializeGroupRestrictions(array $groups):string {
339
		return \json_encode($groups);
340
	}
341
342
	/**
343
	 * Gets all metadata of a backend
344
	 *
345
	 * @param IResource|IRoom $resource
346
	 * @return array
347
	 */
348
	private function getAllMetadataOfBackend($resource):array {
349
		if (!($resource instanceof IMetadataProvider)) {
350
			return [];
351
		}
352
353
		$keys = $resource->getAllAvailableMetadataKeys();
354
		$metadata = [];
355
		foreach($keys as $key) {
356
			$metadata[$key] = $resource->getMetadataForKey($key);
357
		}
358
359
		return $metadata;
360
	}
361
362
	/**
363
	 * @param string $table
364
	 * @param string $foreignKey
365
	 * @param int $id
366
	 * @return array
367
	 */
368
	private function getAllMetadataOfCache(string $table,
369
										   string $foreignKey,
370
										   int $id):array {
371
		$query = $this->dbConnection->getQueryBuilder();
372
		$query->select(['key', 'value'])
373
			->from($table)
374
			->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)));
375
		$stmt = $query->execute();
376
		$rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
377
378
		$metadata = [];
379
		foreach($rows as $row) {
380
			$metadata[$row['key']] = $row['value'];
381
		}
382
383
		return $metadata;
384
	}
385
386
	/**
387
	 * Gets all cached rooms / resources by backend
388
	 *
389
	 * @param $tableName
390
	 * @param $backendId
391
	 * @return array
392
	 */
393
	private function getAllCachedByBackend(string $tableName,
394
										   string $backendId):array {
395
		$query = $this->dbConnection->getQueryBuilder();
396
		$query->select('resource_id')
397
			->from($tableName)
398
			->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)));
399
		$stmt = $query->execute();
400
401
		return array_map(function($row) {
402
			return $row['resource_id'];
403
		}, $stmt->fetchAll(\PDO::FETCH_NAMED));
404
	}
405
406
	/**
407
	 * @param $principalPrefix
408
	 * @param $principalUri
409
	 */
410
	private function deleteCalendarDataForResource(string $principalPrefix,
411
												   string $principalUri):void {
412
		$calendar = $this->calDavBackend->getCalendarByUri(
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $calendar is correct as $this->calDavBackend->ge...E_BOOKING_CALENDAR_URI) targeting OCA\DAV\CalDAV\CalDavBackend::getCalendarByUri() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
413
			implode('/', [$principalPrefix, $principalUri]),
414
			CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI);
415
416
		if ($calendar !== null) {
0 ignored issues
show
introduced by
The condition $calendar !== null is always false.
Loading history...
417
			$this->calDavBackend->deleteCalendar($calendar['id']);
418
		}
419
	}
420
421
	/**
422
	 * @param $table
423
	 * @param $backendId
424
	 * @param $resourceId
425
	 * @return int
426
	 */
427
	private function getIdForBackendAndResource(string $table,
428
												string $backendId,
429
												string $resourceId):int {
430
		$query = $this->dbConnection->getQueryBuilder();
431
		$query->select('id')
432
			->from($table)
433
			->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)))
434
			->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId)));
435
		$stmt = $query->execute();
436
437
		return $stmt->fetch(\PDO::FETCH_NAMED)['id'];
438
	}
439
}
440