Completed
Push — master ( e5ee26...c91a04 )
by Maxence
01:58
created

IndexService   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 399
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 15

Importance

Changes 0
Metric Value
wmc 48
lcom 1
cbo 15
dl 0
loc 399
rs 8.5599
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A setRunner() 0 3 1
A updateRunner() 0 7 2
A indexProviderContentFromUser() 0 11 1
B updateDocumentsWithCurrIndex() 0 29 6
A isDocumentUpToDate() 0 14 4
A getProviderIndexFromProvider() 0 5 1
A indexChunks() 0 23 5
A indexChunk() 0 13 2
A filterDocumentsToIndex() 0 15 4
A updateDocument() 0 22 4
A updateIndexes() 0 10 3
A updateIndex() 0 23 5
A updateIndexesStatus() 0 19 4
A getIndexes() 0 3 1
A getQueuedIndexes() 0 3 1
A resetIndex() 0 23 3

How to fix   Complexity   

Complex Class

Complex classes like IndexService 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 IndexService, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * FullTextSearch - Full text search framework for Nextcloud
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Maxence Lange <[email protected]>
9
 * @copyright 2018
10
 * @license GNU AGPL version 3 or any later version
11
 *
12
 * This program is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License as
14
 * published by the Free Software Foundation, either version 3 of the
15
 * License, or (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License
23
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
24
 *
25
 */
26
27
namespace OCA\FullTextSearch\Service;
28
29
use Exception;
30
use OCA\FullTextSearch\Db\IndexesRequest;
31
use OCA\FullTextSearch\Exceptions\DatabaseException;
32
use OCA\FullTextSearch\Exceptions\IndexDoesNotExistException;
33
use OCA\FullTextSearch\Exceptions\InterruptException;
34
use OCA\FullTextSearch\Exceptions\NoResultException;
35
use OCA\FullTextSearch\Exceptions\TickDoesNotExistException;
36
use OCA\FullTextSearch\IFullTextSearchPlatform;
37
use OCA\FullTextSearch\IFullTextSearchProvider;
38
use OCA\FullTextSearch\Model\ExtendedIndex;
39
use OCA\FullTextSearch\Model\Index;
40
use OCA\FullTextSearch\Model\IndexDocument;
41
use OCA\FullTextSearch\Model\IndexOptions;
42
use OCA\FullTextSearch\Model\ProviderIndexes;
43
use OCA\FullTextSearch\Model\Runner;
44
45
class IndexService {
46
47
	/** @var IndexesRequest */
48
	private $indexesRequest;
49
50
	/** @var ConfigService */
51
	private $configService;
52
53
	/** @var ProviderService */
54
	private $providerService;
55
56
	/** @var PlatformService */
57
	private $platformService;
58
59
	/** @var MiscService */
60
	private $miscService;
61
62
63
	/** @var Runner */
64
	private $runner = null;
65
66
	/**
67
	 * IndexService constructor.
68
	 *
69
	 * @param IndexesRequest $indexesRequest
70
	 * @param ConfigService $configService
71
	 * @param ProviderService $providerService
72
	 * @param PlatformService $platformService
73
	 * @param MiscService $miscService
74
	 */
75
	public function __construct(
76
		IndexesRequest $indexesRequest, ConfigService $configService,
77
		ProviderService $providerService,
78
		PlatformService $platformService, MiscService $miscService
79
	) {
80
		$this->indexesRequest = $indexesRequest;
81
		$this->configService = $configService;
82
		$this->providerService = $providerService;
83
		$this->platformService = $platformService;
84
		$this->miscService = $miscService;
85
	}
86
87
88
	/**
89
	 * @param Runner $runner
90
	 */
91
	public function setRunner(Runner $runner) {
92
		$this->runner = $runner;
93
	}
94
95
96
	/**
97
	 * @param $action
98
	 *
99
	 * @throws InterruptException
100
	 * @throws TickDoesNotExistException
101
	 */
102
	private function updateRunner($action) {
103
		if ($this->runner === null) {
104
			return;
105
		}
106
107
		$this->runner->update($action);
108
	}
109
110
111
	/**
112
	 * @param IFullTextSearchPlatform $platform
113
	 * @param IFullTextSearchProvider $provider
114
	 * @param string $userId
115
	 * @param IndexOptions $options
116
	 *
117
	 * @throws InterruptException
118
	 * @throws TickDoesNotExistException
119
	 * @throws Exception
120
	 */
121
	public function indexProviderContentFromUser(
122
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, $userId, $options
123
	) {
124
		$this->updateRunner('generateIndex' . $provider->getName());
125
		$documents = $provider->generateIndexableDocuments($userId);
126
127
		//$maxSize = sizeof($documents);
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
128
129
		$toIndex = $this->updateDocumentsWithCurrIndex($provider, $documents, $options);
130
		$this->indexChunks($platform, $provider, $toIndex);
131
	}
132
133
134
	/**
135
	 * @param IFullTextSearchProvider $provider
136
	 * @param IndexDocument[] $documents
137
	 * @param IndexOptions $options
138
	 *
139
	 * @return IndexDocument[]
140
	 * @throws InterruptException
141
	 * @throws TickDoesNotExistException
142
	 */
143
	private function updateDocumentsWithCurrIndex(
144
		IFullTextSearchProvider $provider, array $documents, $options
145
	) {
146
147
		$currIndex = $this->getProviderIndexFromProvider($provider);
148
		$result = [];
149
		foreach ($documents as $document) {
150
			$this->updateRunner('compareWithCurrentIndex');
151
152
			$index = $currIndex->getIndex($document->getId());
153
			if ($index === null) {
154
				$index = new Index($document->getProviderId(), $document->getId());
155
				$index->setStatus(Index::INDEX_FULL);
156
				$index->setLastIndex();
157
			}
158
159
			if ($options->getOption('force', false) === true) {
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
160
				$index->setStatus(Index::INDEX_FULL);
161
			}
162
163
			$document->setIndex($index);
164
			if ($options->getOption('force', false) === true
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
165
				|| !$this->isDocumentUpToDate($provider, $document)) {
166
				$result[] = $document;
167
			}
168
		}
169
170
		return $result;
171
	}
172
173
174
	/**
175
	 * @param IFullTextSearchProvider $provider
176
	 * @param IndexDocument $document
177
	 *
178
	 * @return bool
179
	 */
180
	private function isDocumentUpToDate(IFullTextSearchProvider $provider, IndexDocument $document
181
	) {
182
		$index = $document->getIndex();
183
184
		if (!$index->isStatus(Index::INDEX_OK)) {
185
			return false;
186
		}
187
188
		if ($index->isStatus(Index::INDEX_META) || $index->isStatus(Index::INDEX_CONTENT)) {
189
			return false;
190
		}
191
192
		return $provider->isDocumentUpToDate($document);
193
	}
194
195
196
	/**
197
	 * @param IFullTextSearchProvider $provider
198
	 *
199
	 * @return ProviderIndexes
200
	 */
201
	private function getProviderIndexFromProvider(IFullTextSearchProvider $provider) {
202
		$indexes = $this->indexesRequest->getIndexesFromProvider($provider);
203
204
		return new ProviderIndexes($indexes);
205
	}
206
207
208
	/**
209
	 * @param IFullTextSearchPlatform $platform
210
	 * @param IFullTextSearchProvider $provider
211
	 * @param IndexDocument[] $documents
212
	 *
213
	 * @throws Exception
214
	 */
215
	private function indexChunks(
216
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, $documents
217
	) {
218
		$chunkSize = $this->configService->getAppValue(ConfigService::CHUNK_INDEX);
219
220
		$max = sizeof($documents);
221
		for ($i = 0; $i < $max; $i++) {
222
			$this->updateRunner('indexChunk');
223
			try {
224
				$chunk = array_splice($documents, 0, $chunkSize);
225
				$this->indexChunk($platform, $provider, $chunk);
226
227
				/** @var IndexDocument $doc */
228
				foreach ($chunk as $doc) {
229
					$doc->__destruct(); // because.
230
				}
231
			} catch (NoResultException $e) {
232
				return;
233
			} catch (Exception $e) {
234
				throw $e;
235
			}
236
		}
237
	}
238
239
240
	/**
241
	 * @param IFullTextSearchPlatform $platform
242
	 * @param IFullTextSearchProvider $provider
243
	 * @param IndexDocument[] $chunk
244
	 *
245
	 * @throws NoResultException
246
	 * @throws DatabaseException
247
	 */
248
	private function indexChunk(
249
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, $chunk
250
	) {
251
		if (sizeof($chunk) === 0) {
252
			throw new NoResultException();
253
		}
254
255
		$documents = $provider->fillIndexDocuments($chunk);
256
		$toIndex = $this->filterDocumentsToIndex($documents);
257
		$indexes = $platform->indexDocuments($provider, $toIndex);
258
259
		$this->updateIndexes($indexes);
260
	}
261
262
263
	/**
264
	 * @param IndexDocument[] $documents
265
	 *
266
	 * @return array
267
	 */
268
	private function filterDocumentsToIndex($documents) {
269
		$toIndex = [];
270
		foreach ($documents as $document) {
271
			// TODO - rework the index/not_index
272
			$index = $document->getIndex();
273
			$access = $document->getAccess();
274
275
			if ($access !== null && !$index->isStatus(Index::INDEX_IGNORE)) {
276
				$index->setOwnerId($access->getOwnerId());
277
				$toIndex[] = $document;
278
			}
279
		}
280
281
		return $toIndex;
282
	}
283
284
285
	/**
286
	 * @param IFullTextSearchPlatform $platform
287
	 * @param IFullTextSearchProvider $provider
288
	 * @param Index $index
289
	 *
290
	 * @internal param int|string $documentId
291
	 * @throws Exception
292
	 */
293
	public function updateDocument(
294
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, Index $index
295
	) {
296
		$document = null;
297
		if (!$index->isStatus(Index::INDEX_REMOVE)) {
298
			try {
299
				$document = $provider->updateDocument($index);
300
			} catch (Exception $e) {
301
				/** we do nothing, because we're not sure provider manage the right MissingDocumentException */
302
			}
303
		}
304
305
		if ($document === null) {
306
			$platform->deleteIndexes([$index]);
307
			$this->indexesRequest->deleteIndex($index);
308
309
			return;
310
		}
311
312
		$index = $platform->indexDocument($provider, $document);
313
		$this->updateIndex($index);
314
	}
315
316
317
	/**
318
	 * @param Index[] $indexes
319
	 *
320
	 * @throws DatabaseException
321
	 */
322
	public function updateIndexes($indexes) {
323
324
		try {
325
			foreach ($indexes as $index) {
326
				$this->updateIndex($index);
327
			}
328
		} catch (Exception $e) {
329
			throw new DatabaseException($e->getMessage());
330
		}
331
	}
332
333
334
	/**
335
	 * @param Index $index
336
	 *
337
	 * @throws Exception
338
	 */
339
	private function updateIndex(Index $index) {
340
341
		if ($index->isStatus(Index::INDEX_REMOVE)) {
342
343
			if ($index->isStatus(Index::INDEX_DONE)) {
344
				$this->indexesRequest->deleteIndex($index);
345
346
				return;
347
			}
348
349
			$this->indexesRequest->update($index);
350
351
			return;
352
		}
353
354
		if ($index->isStatus(Index::INDEX_DONE)) {
355
			$index->setStatus(Index::INDEX_OK, true);
356
		}
357
358
		if (!$this->indexesRequest->update($index)) {
359
			$this->indexesRequest->create($index);
360
		}
361
	}
362
363
364
	/**
365
	 * @param string $providerId
366
	 * @param array $documentIds
367
	 * @param int $status
368
	 * @param bool $reset
369
	 *
370
	 * @throws DatabaseException
371
	 */
372
	public function updateIndexesStatus($providerId, $documentIds, $status, $reset = false) {
373
		if ($reset === true) {
374
			$this->indexesRequest->updateStatus($providerId, $documentIds, $status);
375
376
			return;
377
		}
378
379
		try {
380
			$all = $this->getIndexes($providerId, $documentIds);
381
		} catch (IndexDoesNotExistException $e) {
382
			return;
383
		}
384
385
		foreach ($all as $curr) {
386
			$curr->setStatus($status);
387
			$this->updateIndexes([$curr]);
388
		}
389
390
	}
391
392
393
	/**
394
	 * @param string $providerId
395
	 * @param array $documentId
396
	 *
397
	 * @return ExtendedIndex[]
398
	 * @throws IndexDoesNotExistException
399
	 */
400
	public function getIndexes($providerId, $documentId) {
401
		return $this->indexesRequest->getIndexes($providerId, $documentId);
402
	}
403
404
405
	/**
406
	 * @return Index[]
407
	 */
408
	public function getQueuedIndexes() {
409
		return $this->indexesRequest->getQueuedIndexes();
410
	}
411
412
413
	/**
414
	 * @param string $providerId
415
	 *
416
	 * @throws Exception
417
	 */
418
	public function resetIndex($providerId = '') {
419
		$platform = $this->platformService->getPlatform();
420
421
		if ($providerId === '') {
422
			$platform->resetIndex();
423
			$this->providerService->setProvidersAsNotIndexed();
424
			$this->indexesRequest->reset();
425
426
			return;
427
		} else {
428
			$providers = [$this->providerService->getProvider($providerId)];
429
		}
430
431
		foreach ($providers AS $provider) {
432
			// TODO: need to specify the map to remove
433
			// TODO: need to remove entries with type=providerId
434
			$provider->onResettingIndex($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<OCA\FullTextSearch\Service\IndexService>, but the function expects a object<OCA\FullTextSearc...FullTextSearchPlatform>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
435
436
			$platform->resetIndex();
437
			$this->providerService->setProviderAsIndexed($provider, false);
438
			$this->indexesRequest->deleteFromProviderId($provider->getId());
439
		}
440
	}
441
442
443
}