Completed
Push — master ( 8c9e58...167f46 )
by Morris
55:27 queued 20:19
created

Sync   C

Complexity

Total Complexity 45

Size/Duplication

Total Lines 340
Duplicated Lines 2.94 %

Coupling/Cohesion

Components 1
Dependencies 16

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 10
loc 340
rs 6.8437
wmc 45
lcom 1
cbo 16

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A updateInterval() 0 11 3
A getMinPagingSize() 0 12 3
C run() 0 35 7
A runCycle() 0 23 2
A getCycle() 0 20 4
A setCycle() 0 4 1
A determineNextCycle() 0 18 4
A qualifiesToRun() 0 7 2
A increaseOffset() 0 5 1
B getNextPrefix() 0 18 5
F setArgument() 10 81 12

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Sync often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Sync, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2017 Arthur Schiwon <[email protected]>
4
 *
5
 * @author Arthur Schiwon <[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\User_LDAP\Jobs;
25
26
use OC\BackgroundJob\TimedJob;
27
use OC\ServerNotAvailableException;
28
use OCA\User_LDAP\Access;
29
use OCA\User_LDAP\AccessFactory;
30
use OCA\User_LDAP\Configuration;
31
use OCA\User_LDAP\Connection;
32
use OCA\User_LDAP\ConnectionFactory;
33
use OCA\User_LDAP\FilesystemHelper;
34
use OCA\User_LDAP\Helper;
35
use OCA\User_LDAP\LDAP;
36
use OCA\User_LDAP\LogWrapper;
37
use OCA\User_LDAP\Mapping\UserMapping;
38
use OCA\User_LDAP\User\Manager;
39
use OCP\IAvatarManager;
40
use OCP\IConfig;
41
use OCP\IDBConnection;
42
use OCP\Image;
43
use OCP\IUserManager;
44
use OCP\Notification\IManager;
45
46
class Sync extends TimedJob {
47
	const MAX_INTERVAL = 12 * 60 * 60; // 12h
48
	const MIN_INTERVAL = 30 * 60; // 30min
49
	/** @var  Helper */
50
	protected $ldapHelper;
51
	/** @var  LDAP */
52
	protected $ldap;
53
	/** @var  Manager */
54
	protected $userManager;
55
	/** @var UserMapping */
56
	protected $mapper;
57
	/** @var  IConfig */
58
	protected $config;
59
	/** @var  IAvatarManager */
60
	protected $avatarManager;
61
	/** @var  IDBConnection */
62
	protected $dbc;
63
	/** @var  IUserManager */
64
	protected $ncUserManager;
65
	/** @var  IManager */
66
	protected $notificationManager;
67
	/** @var ConnectionFactory */
68
	protected $connectionFactory;
69
	/** @var AccessFactory */
70
	protected $accessFactory;
71
72
	public function __construct() {
73
		$this->setInterval(
74
			\OC::$server->getConfig()->getAppValue(
75
				'user_ldap',
76
				'background_sync_interval',
77
				self::MIN_INTERVAL
78
			)
79
		);
80
	}
81
82
	/**
83
	 * updates the interval
84
	 *
85
	 * the idea is to adjust the interval depending on the amount of known users
86
	 * and the attempt to update each user one day. At most it would run every
87
	 * 30 minutes, and at least every 12 hours.
88
	 */
89
	public function updateInterval() {
90
		$minPagingSize = $this->getMinPagingSize();
91
		$mappedUsers = $this->mapper->count();
92
93
		$runsPerDay = ($minPagingSize === 0 || $mappedUsers === 0) ? self::MAX_INTERVAL
94
			: $mappedUsers / $minPagingSize;
95
		$interval = floor(24 * 60 * 60 / $runsPerDay);
96
		$interval = min(max($interval, self::MIN_INTERVAL), self::MAX_INTERVAL);
97
98
		$this->config->setAppValue('user_ldap', 'background_sync_interval', $interval);
99
	}
100
101
	/**
102
	 * returns the smallest configured paging size
103
	 * @return int
104
	 */
105
	protected function getMinPagingSize() {
106
		$configKeys = $this->config->getAppKeys('user_ldap');
107
		$configKeys = array_filter($configKeys, function($key) {
108
			return strpos($key, 'ldap_paging_size') !== false;
109
		});
110
		$minPagingSize = null;
111
		foreach ($configKeys as $configKey) {
112
			$pagingSize = $this->config->getAppValue('user_ldap', $configKey, $minPagingSize);
113
			$minPagingSize = $minPagingSize === null ? $pagingSize : min($minPagingSize, $pagingSize);
114
		}
115
		return (int)$minPagingSize;
116
	}
117
118
	/**
119
	 * @param array $argument
120
	 */
121
	public function run($argument) {
122
		$this->setArgument($argument);
123
124
		$isBackgroundJobModeAjax = $this->config
125
				->getAppValue('core', 'backgroundjobs_mode', 'ajax') === 'ajax';
126
		if($isBackgroundJobModeAjax) {
127
			return;
128
		}
129
130
		$cycleData = $this->getCycle();
131
		if($cycleData === null) {
132
			$cycleData = $this->determineNextCycle();
133
			if($cycleData === null) {
134
				$this->updateInterval();
135
				return;
136
			}
137
		}
138
139
		if(!$this->qualifiesToRun($cycleData)) {
140
			$this->updateInterval();
141
			return;
142
		}
143
144
		try {
145
			$expectMoreResults = $this->runCycle($cycleData);
146
			if ($expectMoreResults) {
147
				$this->increaseOffset($cycleData);
148
			} else {
149
				$this->determineNextCycle($cycleData);
150
			}
151
			$this->updateInterval();
152
		} catch (ServerNotAvailableException $e) {
153
			$this->determineNextCycle($cycleData);
154
		}
155
	}
156
157
	/**
158
	 * @param array $cycleData
159
	 * @return bool whether more results are expected from the same configuration
160
	 */
161
	public function runCycle($cycleData) {
162
		$connection = $this->connectionFactory->get($cycleData['prefix']);
163
		$access = $this->accessFactory->get($connection);
164
		$access->setUserMapper($this->mapper);
165
166
		$filter = $access->combineFilterWithAnd(array(
167
			$access->connection->ldapUserFilter,
168
			$access->connection->ldapUserDisplayName . '=*',
169
			$access->getFilterPartForUserSearch('')
170
		));
171
		$results = $access->fetchListOfUsers(
172
			$filter,
173
			$access->userManager->getAttributes(),
174
			$connection->ldapPagingSize,
175
			$cycleData['offset'],
176
			true
177
		);
178
179
		if($connection->ldapPagingSize === 0) {
180
			return true;
181
		}
182
		return count($results) >= $connection->ldapPagingSize;
183
	}
184
185
	/**
186
	 * returns the info about the current cycle that should be run, if any,
187
	 * otherwise null
188
	 *
189
	 * @return array|null
190
	 */
191
	public function getCycle() {
192
		$prefixes = $this->ldapHelper->getServerConfigurationPrefixes(true);
193
		if(count($prefixes) === 0) {
194
			return null;
195
		}
196
197
		$cycleData = [
198
			'prefix' => $this->config->getAppValue('user_ldap', 'background_sync_prefix', null),
199
			'offset' => (int)$this->config->getAppValue('user_ldap', 'background_sync_offset', 0),
200
		];
201
202
		if(
203
			$cycleData['prefix'] !== null
204
			&& in_array($cycleData['prefix'], $prefixes)
205
		) {
206
			return $cycleData;
207
		}
208
209
		return null;
210
	}
211
212
	/**
213
	 * Save the provided cycle information in the DB
214
	 *
215
	 * @param array $cycleData
216
	 */
217
	public function setCycle(array $cycleData) {
218
		$this->config->setAppValue('user_ldap', 'background_sync_prefix', $cycleData['prefix']);
219
		$this->config->setAppValue('user_ldap', 'background_sync_offset', $cycleData['offset']);
220
	}
221
222
	/**
223
	 * returns data about the next cycle that should run, if any, otherwise
224
	 * null. It also always goes for the next LDAP configuration!
225
	 *
226
	 * @param array|null $cycleData the old cycle
227
	 * @return array|null
228
	 */
229
	public function determineNextCycle(array $cycleData = null) {
230
		$prefixes = $this->ldapHelper->getServerConfigurationPrefixes(true);
231
		if(count($prefixes) === 0) {
232
			return null;
233
		}
234
235
		// get the next prefix in line and remember it
236
		$oldPrefix = $cycleData === null ? null : $cycleData['prefix'];
237
		$prefix = $this->getNextPrefix($oldPrefix);
238
		if($prefix === null) {
239
			return null;
240
		}
241
		$cycleData['prefix'] = $prefix;
242
		$cycleData['offset'] = 0;
243
		$this->setCycle(['prefix' => $prefix, 'offset' => 0]);
244
245
		return $cycleData;
246
	}
247
248
	/**
249
	 * Checks whether the provided cycle should be run. Currently only the
250
	 * last configuration change goes into account (at least one hour).
251
	 *
252
	 * @param $cycleData
253
	 * @return bool
254
	 */
255
	public function qualifiesToRun($cycleData) {
256
		$lastChange = $this->config->getAppValue('user_ldap', $cycleData['prefix'] . '_lastChange', 0);
257
		if((time() - $lastChange) > 60 * 30) {
258
			return true;
259
		}
260
		return false;
261
	}
262
263
	/**
264
	 * increases the offset of the current cycle for the next run
265
	 *
266
	 * @param $cycleData
267
	 */
268
	protected function increaseOffset($cycleData) {
269
		$ldapConfig = new Configuration($cycleData['prefix']);
270
		$cycleData['offset'] += (int)$ldapConfig->ldapPagingSize;
271
		$this->setCycle($cycleData);
272
	}
273
274
	/**
275
	 * determines the next configuration prefix based on the last one (if any)
276
	 *
277
	 * @param string|null $lastPrefix
278
	 * @return string|null
279
	 */
280
	protected function getNextPrefix($lastPrefix) {
281
		$prefixes = $this->ldapHelper->getServerConfigurationPrefixes(true);
282
		$noOfPrefixes = count($prefixes);
283
		if($noOfPrefixes === 0) {
284
			return null;
285
		}
286
		$i = $lastPrefix === null ? false : array_search($lastPrefix, $prefixes, true);
287
		if($i === false) {
288
			$i = -1;
289
		} else {
290
			$i++;
291
		}
292
293
		if(!isset($prefixes[$i])) {
294
			$i = 0;
295
		}
296
		return $prefixes[$i];
297
	}
298
299
	/**
300
	 * "fixes" DI
301
	 *
302
	 * @param array $argument
303
	 */
304
	public function setArgument($argument) {
305 View Code Duplication
		if(isset($argument['config'])) {
306
			$this->config = $argument['config'];
307
		} else {
308
			$this->config = \OC::$server->getConfig();
309
		}
310
311
		if(isset($argument['helper'])) {
312
			$this->ldapHelper = $argument['helper'];
313
		} else {
314
			$this->ldapHelper = new Helper($this->config);
315
		}
316
317
		if(isset($argument['ldapWrapper'])) {
318
			$this->ldap = $argument['ldapWrapper'];
319
		} else {
320
			$this->ldap = new LDAP();
321
		}
322
323
		if(isset($argument['avatarManager'])) {
324
			$this->avatarManager = $argument['avatarManager'];
325
		} else {
326
			$this->avatarManager = \OC::$server->getAvatarManager();
327
		}
328
329 View Code Duplication
		if(isset($argument['dbc'])) {
330
			$this->dbc = $argument['dbc'];
331
		} else {
332
			$this->dbc = \OC::$server->getDatabaseConnection();
333
		}
334
335
		if(isset($argument['ncUserManager'])) {
336
			$this->ncUserManager = $argument['ncUserManager'];
337
		} else {
338
			$this->ncUserManager = \OC::$server->getUserManager();
339
		}
340
341
		if(isset($argument['notificationManager'])) {
342
			$this->notificationManager = $argument['notificationManager'];
343
		} else {
344
			$this->notificationManager = \OC::$server->getNotificationManager();
345
		}
346
347
		if(isset($argument['userManager'])) {
348
			$this->userManager = $argument['userManager'];
349
		} else {
350
			$this->userManager = new Manager(
351
				$this->config,
352
				new FilesystemHelper(),
353
				new LogWrapper(),
354
				$this->avatarManager,
355
				new Image(),
356
				$this->dbc,
357
				$this->ncUserManager,
358
				$this->notificationManager
359
			);
360
		}
361
362
		if(isset($argument['mapper'])) {
363
			$this->mapper = $argument['mapper'];
364
		} else {
365
			$this->mapper = new UserMapping($this->dbc);
366
		}
367
		
368
		if(isset($argument['connectionFactory'])) {
369
			$this->connectionFactory = $argument['connectionFactory'];
370
		} else {
371
			$this->connectionFactory = new ConnectionFactory($this->ldap);
372
		}
373
374
		if(isset($argument['accessFactory'])) {
375
			$this->accessFactory = $argument['accessFactory'];
376
		} else {
377
			$this->accessFactory = new AccessFactory(
378
				$this->ldap,
379
				$this->userManager,
380
				$this->ldapHelper,
381
				$this->config
382
			);
383
		}
384
	}
385
}
386