Completed
Push — master ( 8a0321...725011 )
by Maxence
05:00
created

IndexService   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 382
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Importance

Changes 0
Metric Value
wmc 45
lcom 1
cbo 14
dl 0
loc 382
rs 8.3673
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A setRunner() 0 3 1
A updateRunner() 0 7 2
A indexProviderContentFromUser() 0 11 1
B updateDocumentsWithCurrIndex() 0 25 4
A isDocumentUpToDate() 0 14 4
A getProviderIndexFromProvider() 0 5 1
B indexChunks() 0 24 5
A indexChunk() 0 13 2
B updateDocument() 0 23 4
A updateIndexes() 0 10 3
B updateIndex() 0 23 5
A updateIndexStatus() 0 12 2
A getIndex() 0 3 1
A getQueuedIndexes() 0 3 1
A resetIndex() 0 19 3
A __construct() 0 11 1
B filterDocumentsToIndex() 0 15 5

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 OC\User\NoUserException;
31
use OCA\FullTextSearch\Db\IndexesRequest;
32
use OCA\FullTextSearch\Exceptions\DatabaseException;
33
use OCA\FullTextSearch\Exceptions\IndexDoesNotExistException;
34
use OCA\FullTextSearch\Exceptions\InterruptException;
35
use OCA\FullTextSearch\Exceptions\NoResultException;
36
use OCA\FullTextSearch\Exceptions\TickDoesNotExistException;
37
use OCA\FullTextSearch\IFullTextSearchPlatform;
38
use OCA\FullTextSearch\IFullTextSearchProvider;
39
use OCA\FullTextSearch\Model\ExtendedIndex;
40
use OCA\FullTextSearch\Model\Index;
41
use OCA\FullTextSearch\Model\IndexDocument;
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
	 *
116
	 * @throws Exception
117
	 */
118
	public function indexProviderContentFromUser(
119
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, $userId
120
	) {
121
		$this->updateRunner('generateIndex' . $provider->getName());
122
		$documents = $provider->generateIndexableDocuments($userId);
123
124
		//$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...
125
126
		$toIndex = $this->updateDocumentsWithCurrIndex($provider, $documents);
127
		$this->indexChunks($platform, $provider, $toIndex);
128
	}
129
130
131
	/**
132
	 * @param IFullTextSearchProvider $provider
133
	 * @param IndexDocument[] $documents
134
	 *
135
	 * @return IndexDocument[]
136
	 * @throws InterruptException
137
	 * @throws TickDoesNotExistException
138
	 */
139
	private function updateDocumentsWithCurrIndex(
140
		IFullTextSearchProvider $provider, array $documents
141
	) {
142
143
		$currIndex = $this->getProviderIndexFromProvider($provider);
144
		$result = [];
145
		foreach ($documents as $document) {
146
			$this->updateRunner('compareWithCurrentIndex');
147
148
			$index = $currIndex->getIndex($document->getId());
149
			if ($index === null) {
150
				$index = new Index($document->getProviderId(), $document->getId());
151
				$index->setStatus(Index::INDEX_FULL);
152
				$index->setLastIndex();
153
			}
154
155
			$document->setIndex($index);
156
			if (!$this->isDocumentUpToDate($provider, $document)) {
157
				$result[] = $document;
158
			}
159
160
		}
161
162
		return $result;
163
	}
164
165
166
	/**
167
	 * @param IFullTextSearchProvider $provider
168
	 * @param IndexDocument $document
169
	 *
170
	 * @return bool
171
	 */
172
	private function isDocumentUpToDate(IFullTextSearchProvider $provider, IndexDocument $document
173
	) {
174
		$index = $document->getIndex();
175
176
		if (!$index->isStatus(Index::INDEX_OK)) {
177
			return false;
178
		}
179
180
		if ($index->isStatus(Index::INDEX_META) || $index->isStatus(Index::INDEX_CONTENT)) {
181
			return false;
182
		}
183
184
		return $provider->isDocumentUpToDate($document);
185
	}
186
187
188
	/**
189
	 * @param IFullTextSearchProvider $provider
190
	 *
191
	 * @return ProviderIndexes
192
	 */
193
	private function getProviderIndexFromProvider(IFullTextSearchProvider $provider) {
194
		$indexes = $this->indexesRequest->getIndexesFromProvider($provider);
195
196
		return new ProviderIndexes($indexes);
197
	}
198
199
200
	/**
201
	 * @param IFullTextSearchPlatform $platform
202
	 * @param IFullTextSearchProvider $provider
203
	 * @param IndexDocument[] $documents
204
	 *
205
	 * @throws Exception
206
	 */
207
	private function indexChunks(
208
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, $documents
209
	) {
210
		$chunkSize = $this->configService->getAppValue(ConfigService::CHUNK_INDEX);
211
212
		$max = sizeof($documents);
213
		for ($i = 0; $i < $max; $i++) {
214
215
			$this->updateRunner('indexChunk');
216
			try {
217
				$chunk = array_splice($documents, 0, $chunkSize);
218
				$this->indexChunk($platform, $provider, $chunk);
219
220
				/** @var IndexDocument $doc */
221
				foreach ($chunk as $doc) {
222
					$doc->__destruct(); // because.
223
				}
224
			} catch (NoResultException $e) {
225
				return;
226
			} catch (Exception $e) {
227
				throw $e;
228
			}
229
		}
230
	}
231
232
233
	/**
234
	 * @param IFullTextSearchPlatform $platform
235
	 * @param IFullTextSearchProvider $provider
236
	 * @param IndexDocument[] $chunk
237
	 *
238
	 * @throws NoResultException
239
	 * @throws DatabaseException
240
	 */
241
	private function indexChunk(
242
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, $chunk
243
	) {
244
		if (sizeof($chunk) === 0) {
245
			throw new NoResultException();
246
		}
247
248
		$documents = $provider->fillIndexDocuments($chunk);
249
		$toIndex = $this->filterDocumentsToIndex($documents);
250
		$indexes = $platform->indexDocuments($provider, $toIndex);
251
252
		$this->updateIndexes($indexes);
253
	}
254
255
256
	/**
257
	 * @param IndexDocument[] $documents
258
	 *
259
	 * @return array
260
	 */
261
	private function filterDocumentsToIndex($documents) {
262
		$toIndex = [];
263
		foreach ($documents as $document) {
264
			// TODO - rework the index/not_index
265
			$index = $document->getIndex();
266
			$access = $document->getAccess();
267
268
			if ($access !== null && $index !== null && !$index->isStatus(Index::INDEX_IGNORE)) {
269
				$index->setOwnerId($access->getOwnerId());
270
				$toIndex[] = $document;
271
			}
272
		}
273
274
		return $toIndex;
275
	}
276
277
278
	/**
279
	 * @param IFullTextSearchPlatform $platform
280
	 * @param IFullTextSearchProvider $provider
281
	 * @param Index $index
282
	 *
283
	 * @internal param int|string $documentId
284
	 * @throws Exception
285
	 */
286
	public function updateDocument(
287
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, Index $index
288
	) {
289
		$document = null;
290
291
		if (!$index->isStatus(Index::INDEX_REMOVE)) {
292
			try {
293
				$document = $provider->updateDocument($index);
294
			} catch (Exception $e) {
295
				/** we do nothing, because we're not sure provider manage the right MissingDocumentException */
296
			}
297
		}
298
299
		if ($document === null) {
300
			$platform->deleteIndexes([$index]);
301
			$this->indexesRequest->deleteIndex($index);
302
303
			return;
304
		}
305
306
		$index = $platform->indexDocument($provider, $document);
307
		$this->updateIndex($index);
308
	}
309
310
311
	/**
312
	 * @param Index[] $indexes
313
	 *
314
	 * @throws DatabaseException
315
	 */
316
	public function updateIndexes($indexes) {
317
318
		try {
319
			foreach ($indexes as $index) {
320
				$this->updateIndex($index);
321
			}
322
		} catch (Exception $e) {
323
			throw new DatabaseException($e->getMessage());
324
		}
325
	}
326
327
328
	/**
329
	 * @param Index $index
330
	 *
331
	 * @throws Exception
332
	 */
333
	private function updateIndex(Index $index) {
334
335
		if ($index->isStatus(Index::INDEX_REMOVE)) {
336
337
			if ($index->isStatus(Index::INDEX_DONE)) {
338
				$this->indexesRequest->deleteIndex($index);
339
340
				return;
341
			}
342
343
			$this->indexesRequest->update($index);
344
345
			return;
346
		}
347
348
		if ($index->isStatus(Index::INDEX_DONE)) {
349
			$index->setStatus(Index::INDEX_OK, true);
350
		}
351
352
		if (!$this->indexesRequest->update($index)) {
353
			$this->indexesRequest->create($index);
354
		}
355
	}
356
357
358
	/**
359
	 * @param string $providerId
360
	 * @param string|int $documentId
361
	 * @param int $status
362
	 * @param bool $reset
363
	 *
364
	 * @throws DatabaseException
365
	 */
366
	public function updateIndexStatus($providerId, $documentId, $status, $reset = false) {
0 ignored issues
show
Unused Code introduced by
The parameter $reset is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
367
		try {
368
			$curr = $this->getIndex($providerId, $documentId);
369
		} catch (IndexDoesNotExistException $e) {
370
			return;
371
//			$curr = new Index($providerId, $documentId);
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% 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...
372
//			$curr->setStatus(Index::INDEX_FULL);
373
		}
374
375
		$curr->setStatus($status);
376
		$this->updateIndexes([$curr]);
377
	}
378
379
380
	/**
381
	 * @param string $providerId
382
	 * @param string|int $documentId
383
	 *
384
	 * @return ExtendedIndex
385
	 * @throws IndexDoesNotExistException
386
	 */
387
	public function getIndex($providerId, $documentId) {
388
		return $this->indexesRequest->getIndex($providerId, $documentId);
389
	}
390
391
392
	/**
393
	 * @return Index[]
394
	 */
395
	public function getQueuedIndexes() {
396
		return $this->indexesRequest->getQueuedIndexes();
397
	}
398
399
400
	/**
401
	 * @param string $providerId
402
	 *
403
	 * @throws Exception
404
	 */
405
	public function resetIndex($providerId = '') {
406
		$platform = $this->platformService->getPlatform();
407
408
		if ($providerId === '') {
409
			$platform->resetIndex(null);
410
			$this->providerService->setProvidersAsNotIndexed();
411
			$this->indexesRequest->reset();
412
413
			return;
414
		} else {
415
			$providers = [$this->providerService->getProvider($providerId)];
416
		}
417
418
		foreach ($providers AS $provider) {
419
			$platform->resetIndex($provider);
420
			$this->providerService->setProviderAsIndexed($provider, false);
421
			$this->indexesRequest->deleteFromProviderId($provider->getId());
422
		}
423
	}
424
425
426
}