Completed
Push — master ( 8110a3...fe2a60 )
by Morris
18:07 queued 10s
created

runResources()   C

Complexity

Conditions 11
Paths 144

Size

Total Lines 48

Duplication

Lines 48
Ratio 100 %

Importance

Changes 0
Metric Value
cc 11
nc 144
nop 0
dl 48
loc 48
rs 6.95
c 0
b 0
f 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
 * @copyright 2018, 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\Resource\IManager as IResourceManager;
30
use OCP\Calendar\Resource\IResource;
31
use OCP\Calendar\Room\IManager as IRoomManager;
32
use OCP\Calendar\Room\IRoom;
33
use OCP\IDBConnection;
34
35
class UpdateCalendarResourcesRoomsBackgroundJob extends TimedJob {
36
37
	/** @var IResourceManager */
38
	private $resourceManager;
39
40
	/** @var IRoomManager */
41
	private $roomManager;
42
43
	/** @var IDBConnection */
44
	private $db;
45
46
	/** @var CalDavBackend */
47
	private $calDavBackend;
48
49
	/** @var string */
50
	private $resourceDbTable;
51
52
	/** @var string */
53
	private $resourcePrincipalUri;
54
55
	/** @var string */
56
	private $roomDbTable;
57
58
	/** @var string */
59
	private $roomPrincipalUri;
60
61
	/**
62
	 * UpdateCalendarResourcesRoomsBackgroundJob constructor.
63
	 *
64
	 * @param IResourceManager $resourceManager
65
	 * @param IRoomManager $roomManager
66
	 * @param IDBConnection $dbConnection
67
	 * @param CalDavBackend $calDavBackend
68
	 */
69
	public function __construct(IResourceManager $resourceManager, IRoomManager $roomManager,
70
								IDBConnection $dbConnection, CalDavBackend $calDavBackend) {
71
		$this->resourceManager = $resourceManager;
72
		$this->roomManager = $roomManager;
73
		$this->db = $dbConnection;
74
		$this->calDavBackend = $calDavBackend;
75
		$this->resourceDbTable = 'calendar_resources';
76
		$this->resourcePrincipalUri = 'principals/calendar-resources';
77
		$this->roomDbTable = 'calendar_rooms';
78
		$this->roomPrincipalUri = 'principals/calendar-rooms';
79
80
		// run once an hour
81
		$this->setInterval(60 * 60);
82
	}
83
84
	/**
85
	 * @param $argument
86
	 */
87
	public function run($argument) {
88
		$this->runResources();
89
		$this->runRooms();
90
	}
91
92
	/**
93
	 * run timed job for resources
94
	 */
95 View Code Duplication
	private function runResources() {
96
		$resourceBackends = $this->resourceManager->getBackends();
97
		$cachedResources = $this->getCached($this->resourceDbTable);
98
		$cachedResourceIds = $this->getCachedResourceIds($cachedResources);
99
100
		$remoteResourceIds = [];
101
		foreach($resourceBackends as $resourceBackend) {
102
			try {
103
				$remoteResourceIds[$resourceBackend->getBackendIdentifier()] =
104
					$resourceBackend->listAllResources();
105
			} catch(BackendTemporarilyUnavailableException $ex) {
106
				// If the backend is temporarily unavailable
107
				// ignore this backend in this execution
108
				unset($cachedResourceIds[$resourceBackend->getBackendIdentifier()]);
109
			}
110
		}
111
112
		$sortedResources = $this->sortByNewDeletedExisting($cachedResourceIds, $remoteResourceIds);
113
114
		foreach($sortedResources['new'] as $backendId => $newResources) {
115
			foreach ($newResources as $newResource) {
116
				$backend = $this->resourceManager->getBackend($backendId);
117
				if ($backend === null) {
118
					continue;
119
				}
120
121
				$resource = $backend->getResource($newResource);
122
				$this->addToCache($this->resourceDbTable, $resource);
0 ignored issues
show
Bug introduced by
It seems like $resource defined by $backend->getResource($newResource) on line 121 can be null; however, OCA\DAV\BackgroundJob\Up...groundJob::addToCache() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
123
			}
124
		}
125
		foreach($sortedResources['deleted'] as $backendId => $deletedResources) {
126
			foreach ($deletedResources as $deletedResource) {
127
				$this->deleteFromCache($this->resourceDbTable,
128
					$this->resourcePrincipalUri, $backendId, $deletedResource);
129
			}
130
		}
131
		foreach($sortedResources['edited'] as $backendId => $editedResources) {
132
			foreach ($editedResources as $editedResource) {
133
				$backend = $this->resourceManager->getBackend($backendId);
134
				if ($backend === null) {
135
					continue;
136
				}
137
138
				$resource = $backend->getResource($editedResource);
139
				$this->updateCache($this->resourceDbTable, $resource);
0 ignored issues
show
Bug introduced by
It seems like $resource defined by $backend->getResource($editedResource) on line 138 can be null; however, OCA\DAV\BackgroundJob\Up...roundJob::updateCache() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
140
			}
141
		}
142
	}
143
144
	/**
145
	 * run timed job for rooms
146
	 */
147 View Code Duplication
	private function runRooms() {
148
		$roomBackends = $this->roomManager->getBackends();
149
		$cachedRooms = $this->getCached($this->roomDbTable);
150
		$cachedRoomIds = $this->getCachedRoomIds($cachedRooms);
151
152
		$remoteRoomIds = [];
153
		foreach($roomBackends as $roomBackend) {
154
			try {
155
				$remoteRoomIds[$roomBackend->getBackendIdentifier()] =
156
					$roomBackend->listAllRooms();
157
			} catch(BackendTemporarilyUnavailableException $ex) {
158
				// If the backend is temporarily unavailable
159
				// ignore this backend in this execution
160
				unset($cachedRoomIds[$roomBackend->getBackendIdentifier()]);
161
			}
162
		}
163
164
		$sortedRooms = $this->sortByNewDeletedExisting($cachedRoomIds, $remoteRoomIds);
165
166
		foreach($sortedRooms['new'] as $backendId => $newRooms) {
167
			foreach ($newRooms as $newRoom) {
168
				$backend = $this->roomManager->getBackend($backendId);
169
				if ($backend === null) {
170
					continue;
171
				}
172
173
				$resource = $backend->getRoom($newRoom);
174
				$this->addToCache($this->roomDbTable, $resource);
0 ignored issues
show
Bug introduced by
It seems like $resource defined by $backend->getRoom($newRoom) on line 173 can be null; however, OCA\DAV\BackgroundJob\Up...groundJob::addToCache() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
175
			}
176
		}
177
		foreach($sortedRooms['deleted'] as $backendId => $deletedRooms) {
178
			foreach ($deletedRooms as $deletedRoom) {
179
				$this->deleteFromCache($this->roomDbTable,
180
					$this->roomPrincipalUri, $backendId, $deletedRoom);
181
			}
182
		}
183
		foreach($sortedRooms['edited'] as $backendId => $editedRooms) {
184
			foreach ($editedRooms as $editedRoom) {
185
				$backend = $this->roomManager->getBackend($backendId);
186
				if ($backend === null) {
187
					continue;
188
				}
189
190
				$resource = $backend->getRoom($editedRoom);
191
				$this->updateCache($this->roomDbTable, $resource);
0 ignored issues
show
Bug introduced by
It seems like $resource defined by $backend->getRoom($editedRoom) on line 190 can be null; however, OCA\DAV\BackgroundJob\Up...roundJob::updateCache() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
192
			}
193
		}
194
	}
195
196
	/**
197
	 * get cached db rows for resources / rooms
198
	 * @param string $tableName
199
	 * @return array
200
	 */
201
	private function getCached($tableName):array {
202
		$query = $this->db->getQueryBuilder();
203
		$query->select('*')->from($tableName);
204
205
		$rows = [];
206
		$stmt = $query->execute();
207
		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
208
			$rows[] = $row;
209
		}
210
211
		return $rows;
212
	}
213
214
	/**
215
	 * @param array $cachedResources
216
	 * @return array
217
	 */
218 View Code Duplication
	private function getCachedResourceIds(array $cachedResources):array {
219
		$cachedResourceIds = [];
220
		foreach ($cachedResources as $cachedResource) {
221
			if (!isset($cachedResourceIds[$cachedResource['backend_id']])) {
222
				$cachedResourceIds[$cachedResource['backend_id']] = [];
223
			}
224
225
			$cachedResourceIds[$cachedResource['backend_id']][] =
226
				$cachedResource['resource_id'];
227
		}
228
229
		return $cachedResourceIds;
230
	}
231
232
	/**
233
	 * @param array $cachedRooms
234
	 * @return array
235
	 */
236 View Code Duplication
	private function getCachedRoomIds(array $cachedRooms):array {
237
		$cachedRoomIds = [];
238
		foreach ($cachedRooms as $cachedRoom) {
239
			if (!isset($cachedRoomIds[$cachedRoom['backend_id']])) {
240
				$cachedRoomIds[$cachedRoom['backend_id']] = [];
241
			}
242
243
			$cachedRoomIds[$cachedRoom['backend_id']][] =
244
				$cachedRoom['resource_id'];
245
		}
246
247
		return $cachedRoomIds;
248
	}
249
250
	/**
251
	 * sort list of ids by whether they appear only in the backend /
252
	 * only in the cache / in both
253
	 *
254
	 * @param array $cached
255
	 * @param array $remote
256
	 * @return array
257
	 */
258
	private function sortByNewDeletedExisting(array $cached, array $remote):array {
259
		$sorted = [
260
			'new' => [],
261
			'deleted' => [],
262
			'edited' => [],
263
		];
264
265
		$backendIds = array_merge(array_keys($cached), array_keys($remote));
266
		foreach($backendIds as $backendId) {
267
			if (!isset($cached[$backendId])) {
268
				$sorted['new'][$backendId] = $remote[$backendId];
269
			} elseif (!isset($remote[$backendId])) {
270
				$sorted['deleted'][$backendId] = $cached[$backendId];
271
			} else {
272
				$sorted['new'][$backendId] = array_diff($remote[$backendId], $cached[$backendId]);
273
				$sorted['deleted'][$backendId] = array_diff($cached[$backendId], $remote[$backendId]);
274
				$sorted['edited'][$backendId] = array_intersect($remote[$backendId], $cached[$backendId]);
275
			}
276
		}
277
278
		return $sorted;
279
	}
280
281
	/**
282
	 * add entry to cache that exists remotely but not yet in cache
283
	 *
284
	 * @param string $table
285
	 * @param IResource|IRoom $remote
286
	 */
287
	private function addToCache($table, $remote) {
288
		$query = $this->db->getQueryBuilder();
289
		$query->insert($table)
290
			->values([
291
				'backend_id' => $query->createNamedParameter($remote->getBackend()->getBackendIdentifier()),
292
				'resource_id' => $query->createNamedParameter($remote->getId()),
293
				'email' => $query->createNamedParameter($remote->getEMail()),
294
				'displayname' => $query->createNamedParameter($remote->getDisplayName()),
295
				'group_restrictions' => $query->createNamedParameter(
296
					$this->serializeGroupRestrictions(
297
						$remote->getGroupRestrictions()
298
					))
299
			])
300
			->execute();
301
	}
302
303
	/**
304
	 * delete entry from cache that does not exist anymore remotely
305
	 *
306
	 * @param string $table
307
	 * @param string $principalUri
308
	 * @param string $backendId
309
	 * @param string $resourceId
310
	 */
311
	private function deleteFromCache($table, $principalUri, $backendId, $resourceId) {
312
		$query = $this->db->getQueryBuilder();
313
		$query->delete($table)
314
			->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)))
315
			->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId)))
316
			->execute();
317
318
		$calendar = $this->calDavBackend->getCalendarByUri($principalUri, implode('-', [$backendId, $resourceId]));
319
		if ($calendar !== null) {
320
			$this->calDavBackend->deleteCalendar($calendar['id']);
321
		}
322
	}
323
324
	/**
325
	 * update an existing entry in cache
326
	 *
327
	 * @param string $table
328
	 * @param IResource|IRoom $remote
329
	 */
330
	private function updateCache($table, $remote) {
331
		$query = $this->db->getQueryBuilder();
332
		$query->update($table)
333
			->set('email', $query->createNamedParameter($remote->getEMail()))
334
			->set('displayname', $query->createNamedParameter($remote->getDisplayName()))
335
			->set('group_restrictions', $query->createNamedParameter(
336
				$this->serializeGroupRestrictions(
337
					$remote->getGroupRestrictions()
338
				)))
339
			->where($query->expr()->eq('backend_id', $query->createNamedParameter($remote->getBackend()->getBackendIdentifier())))
340
			->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($remote->getId())))
341
			->execute();
342
	}
343
344
	/**
345
	 * serialize array of group restrictions to store them in database
346
	 *
347
	 * @param array $groups
348
	 * @return string
349
	 */
350
	private function serializeGroupRestrictions(array $groups):string {
351
		return \json_encode($groups);
352
	}
353
}
354