Completed
Push — master ( dba968...9e87bf )
by Maxence
02:27
created

IndexService::resetErrorFromIndex()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
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\NotIndexableDocumentException;
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\IndexOptions;
43
use OCA\FullTextSearch\Model\ProviderIndexes;
44
use OCA\FullTextSearch\Model\Runner;
45
46
class IndexService {
47
48
	/** @var IndexesRequest */
49
	private $indexesRequest;
50
51
	/** @var ConfigService */
52
	private $configService;
53
54
	/** @var ProviderService */
55
	private $providerService;
56
57
	/** @var PlatformService */
58
	private $platformService;
59
60
	/** @var MiscService */
61
	private $miscService;
62
63
64
	/** @var Runner */
65
	private $runner = null;
66
67
	/** @var array */
68
	private $queuedDeleteIndex = [];
69
70
	/** @var int */
71
	private $currentTotalDocuments = 0;
72
73
74
	/**
75
	 * IndexService constructor.
76
	 *
77
	 * @param IndexesRequest $indexesRequest
78
	 * @param ConfigService $configService
79
	 * @param ProviderService $providerService
80
	 * @param PlatformService $platformService
81
	 * @param MiscService $miscService
82
	 */
83
	public function __construct(
84
		IndexesRequest $indexesRequest, ConfigService $configService,
85
		ProviderService $providerService,
86
		PlatformService $platformService, MiscService $miscService
87
	) {
88
		$this->indexesRequest = $indexesRequest;
89
		$this->configService = $configService;
90
		$this->providerService = $providerService;
91
		$this->platformService = $platformService;
92
		$this->miscService = $miscService;
93
	}
94
95
96
	/**
97
	 * @param Runner $runner
98
	 */
99
	public function setRunner(Runner $runner) {
100
		$this->runner = $runner;
101
	}
102
103
104
	/**
105
	 * @param string $action
106
	 * @param bool $force
107
	 *
108
	 * @throws InterruptException
109
	 * @throws TickDoesNotExistException
110
	 */
111
	private function updateRunnerAction($action, $force = false) {
112
		if ($this->runner === null) {
113
			return;
114
		}
115
116
		$this->runner->updateAction($action, $force);
117
	}
118
119
	/**
120
	 * @param string $info
121
	 * @param string $value
122
	 */
123
	private function updateRunnerInfo($info, $value, $color = '') {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
124
		if ($this->runner === null) {
125
			return;
126
		}
127
128
		$this->runner->setInfo($info, $value, $color);
129
	}
130
131
	/**
132
	 * @param array $data
133
	 */
134
	private function updateRunnerInfoArray($data) {
135
		if ($this->runner === null) {
136
			return;
137
		}
138
139
		$this->runner->setInfoArray($data);
140
	}
141
142
143
	/**
144
	 * @param IFullTextSearchPlatform $platform
145
	 * @param IFullTextSearchProvider $provider
146
	 * @param string $userId
147
	 * @param IndexOptions $options
148
	 *
149
	 * @throws InterruptException
150
	 * @throws TickDoesNotExistException
151
	 * @throws Exception
152
	 */
153
	public function indexProviderContentFromUser(
154
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, $userId, $options
155
	) {
156
		$this->updateRunnerAction('generateIndex' . $provider->getName());
157
		$this->updateRunnerInfoArray(
158
			[
159
				'userId'       => $userId,
160
				'providerId'   => $provider->getId(),
161
				'providerName' => $provider->getName()
162
			]
163
		);
164
165
		$documents = $provider->generateIndexableDocuments($userId);
166
		$this->currentTotalDocuments = sizeof($documents);
167
168
		$this->updateRunnerInfoArray(
169
			[
170
				'documentTotal'   => $this->currentTotalDocuments,
171
				'documentCurrent' => 0
172
			]
173
		);
174
175
		//$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...
176
177
		$toIndex = $this->updateDocumentsWithCurrIndex($provider, $documents, $options);
178
		$this->indexDocuments($platform, $provider, $toIndex, $options);
179
	}
180
181
182
	/**
183
	 * @param IFullTextSearchProvider $provider
184
	 * @param IndexDocument[] $documents
185
	 * @param IndexOptions $options
186
	 *
187
	 * @return IndexDocument[]
188
	 * @throws InterruptException
189
	 * @throws TickDoesNotExistException
190
	 */
191
	private function updateDocumentsWithCurrIndex(
192
		IFullTextSearchProvider $provider, array $documents, IndexOptions $options
193
	) {
194
195
		$currIndex = $this->getProviderIndexFromProvider($provider);
196
		$result = [];
197
		foreach ($documents as $document) {
198
			$this->updateRunnerAction('compareWithCurrentIndex');
199
200
			$index = $currIndex->getIndex($document->getId());
201
			if ($index === null) {
202
				$index = new Index($document->getProviderId(), $document->getId());
203
				$index->setStatus(Index::INDEX_FULL);
204
				$index->setLastIndex();
205
			}
206
207
			if ($options->getOption('errors', '') !== 'ignore' && $index->getErrorCount() > 0) {
208
				continue;
209
			}
210
211
			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...
212
				$index->setStatus(Index::INDEX_FULL);
213
			}
214
215
			$index->resetErrors();
216
			$document->setIndex($index);
217
			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...
218
				|| !$this->isDocumentUpToDate($provider, $document)) {
219
				$result[] = $document;
220
			}
221
		}
222
223
		return $result;
224
	}
225
226
227
	/**
228
	 * @param IFullTextSearchProvider $provider
229
	 * @param IndexDocument $document
230
	 *
231
	 * @return bool
232
	 */
233
	private function isDocumentUpToDate(IFullTextSearchProvider $provider, IndexDocument $document
234
	) {
235
		$index = $document->getIndex();
236
237
		if (!$index->isStatus(Index::INDEX_OK)) {
238
			return false;
239
		}
240
241
		if ($index->isStatus(Index::INDEX_META) || $index->isStatus(Index::INDEX_CONTENT)) {
242
			return false;
243
		}
244
245
		return $provider->isDocumentUpToDate($document);
246
	}
247
248
249
	/**
250
	 * @param IFullTextSearchProvider $provider
251
	 *
252
	 * @return ProviderIndexes
253
	 */
254
	private function getProviderIndexFromProvider(IFullTextSearchProvider $provider) {
255
		$indexes = $this->indexesRequest->getIndexesFromProvider($provider);
256
257
		return new ProviderIndexes($indexes);
258
	}
259
260
261
	/**
262
	 * @param IFullTextSearchPlatform $platform
263
	 * @param IFullTextSearchProvider $provider
264
	 * @param IndexDocument[] $documents
265
	 * @param IndexOptions $options
266
	 *
267
	 * @throws InterruptException
268
	 * @throws TickDoesNotExistException
269
	 * @throws Exception
270
	 */
271
	private function indexDocuments(
272
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, $documents,
273
		IndexOptions $options
0 ignored issues
show
Unused Code introduced by
The parameter $options 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...
274
	) {
275
		while ($document = array_shift($documents)) {
276
			try {
277
278
				$this->updateRunnerInfoArray(
279
					[
280
						'documentCurrent' => ($this->currentTotalDocuments - sizeof($documents) - 1)
281
					]
282
				);
283
				$this->updateRunnerAction('fillDocument', true);
284
				$this->updateRunnerInfoArray(
285
					[
286
						'documentId'    => $document->getId(),
287
						'title'         => '',
288
						'content'       => '',
289
						'status'        => '',
290
						'statusColored' => ''
291
					]
292
				);
293
294
				$provider->fillIndexDocument($document);
0 ignored issues
show
Bug introduced by
The method fillIndexDocument() does not exist on OCA\FullTextSearch\IFullTextSearchProvider. Did you maybe mean fillIndexDocuments()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
295
				$this->updateRunnerInfoArray(
296
					[
297
						'title'   => $document->getTitle(),
298
						'content' => $document->getContentSize()
299
					]
300
				);
301
				$this->filterDocumentBeforeIndex($document);
302
303
			} catch (NotIndexableDocumentException $e) {
304
				continue;
305
			}
306
307
			$index = $this->indexDocument($platform, $provider, $document);
308
309
			$this->updateIndex($index);
310
311
			$document->__destruct();
312
			unset($document);
313
		}
314
	}
315
316
317
	/**
318
	 * @param IndexDocument $document
319
	 *
320
	 * @throws NotIndexableDocumentException
321
	 */
322
	private function filterDocumentBeforeIndex(IndexDocument $document) {
323
		// TODO - rework the index/not_index
324
		$index = $document->getIndex();
325
		$access = $document->getAccess();
326
327
// INDEX_IGNORE is not used anymore, as we use addError()
328
		if ($access === null || $index->isStatus(Index::INDEX_IGNORE)) {
329
			throw new NotIndexableDocumentException();
330
		}
331
332
		$index->setOwnerId($access->getOwnerId());
333
	}
334
335
336
	/**
337
	 * @param IFullTextSearchPlatform $platform
338
	 * @param IFullTextSearchProvider $provider
339
	 * @param IndexDocument $document
340
	 *
341
	 * @return
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use Index.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
342
	 * @throws Exception
343
	 */
344
	public function indexDocument(
345
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, $document
346
	) {
347
		$this->updateRunnerAction('indexDocument', true);
348
		$this->updateRunnerInfoArray(
349
			[
350
				'documentId' => $document->getId(),
351
				'title'      => $document->getTitle(),
352
				'content'    => $document->getContentSize()
353
			]
354
		);
355
356
		$index = null;
357
		try {
358
			$index = $platform->indexDocument($provider, $document);
359
		} catch (Exception $e) {
360
			if ($this->runner->isStrict()) {
361
				throw $e;
362
			}
363
		}
364
365
		return $index;
366
	}
367
368
369
	/**
370
	 * @param IFullTextSearchPlatform $platform
371
	 * @param IFullTextSearchProvider $provider
372
	 * @param Index $index
373
	 *
374
	 * @internal param int|string $documentId
375
	 * @throws Exception
376
	 */
377
	public function updateDocument(
378
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, Index $index
379
	) {
380
		$document = null;
381
		$this->updateRunnerInfoArray(
382
			[
383
				'providerName' => $provider->getName(),
384
				'userId'       => $index->getOwnerId(),
385
			]
386
		);
387
388
		if (!$index->isStatus(Index::INDEX_REMOVE)) {
389
			try {
390
				$document = $provider->updateDocument($index);
391
			} catch (Exception $e) {
392
				/** we do nothing, because we're not sure provider manage the right MissingDocumentException */
393
			}
394
		}
395
396
		if ($document === null) {
397
			$platform->deleteIndexes([$index]);
398
			$this->indexesRequest->deleteIndex($index);
399
400
			return;
401
		}
402
403
		$this->updateRunnerAction('indexDocument', true);
404
		$this->updateRunnerInfoArray(
405
			[
406
				'documentId' => $document->getId(),
407
				'title'      => $document->getTitle(),
408
				'content'    => $document->getContentSize()
409
			]
410
		);
411
412
		$document->getIndex()
413
				 ->resetErrors();
414
		$index = $platform->indexDocument($provider, $document);
415
		$this->updateIndex($index);
416
	}
417
418
419
	/**
420
	 * @param Index[] $indexes
421
	 *
422
	 * @throws DatabaseException
423
	 */
424
	public function updateIndexes($indexes) {
425
		try {
426
			foreach ($indexes as $index) {
427
				$this->updateIndex($index);
428
			}
429
			$this->resetErrorFromQueue();
430
		} catch (Exception $e) {
431
			throw new DatabaseException($e->getMessage());
432
		}
433
	}
434
435
436
	/**
437
	 * @param Index $index
438
	 *
439
	 * @throws Exception
440
	 */
441
	private function updateIndex(Index $index) {
442
443
		$this->updateIndexError($index);
0 ignored issues
show
Unused Code introduced by
The call to the method OCA\FullTextSearch\Servi...ice::updateIndexError() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
444
		if ($index->isStatus(Index::INDEX_REMOVE)) {
445
446
			if ($index->isStatus(Index::INDEX_DONE)) {
447
				$this->indexesRequest->deleteIndex($index);
448
449
				return;
450
			}
451
452
			$this->indexesRequest->update($index);
453
454
			return;
455
		}
456
457
		if ($index->isStatus(Index::INDEX_DONE)) {
458
			$index->setStatus(Index::INDEX_OK, true);
459
		}
460
461
		if (!$this->indexesRequest->update($index)) {
462
			$this->indexesRequest->create($index);
463
		}
464
	}
465
466
467
	private function updateIndexError(Index $index) {
0 ignored issues
show
Unused Code introduced by
The parameter $index 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...
468
469
	}
470
471
472
	/**
473
	 * @param string $providerId
474
	 * @param array $documentIds
475
	 * @param int $status
476
	 * @param bool $reset
477
	 *
478
	 * @throws DatabaseException
479
	 */
480
	public function updateIndexesStatus($providerId, $documentIds, $status, $reset = false) {
481
		if ($reset === true) {
482
			$this->indexesRequest->updateStatus($providerId, $documentIds, $status);
483
484
			return;
485
		}
486
487
		try {
488
			$all = $this->getIndexes($providerId, $documentIds);
489
		} catch (IndexDoesNotExistException $e) {
490
			return;
491
		}
492
493
		foreach ($all as $curr) {
494
			$curr->setStatus($status);
495
			$this->updateIndexes([$curr]);
496
		}
497
498
	}
499
500
501
	/**
502
	 * @param Index $index
503
	 */
504
	public function resetErrorFromIndex(Index $index) {
505
		if (!$this->indexesRequest->resetError($index)) {
506
			$this->queuedDeleteIndex[] = $index;
507
		}
508
	}
509
510
511
	/**
512
	 *
513
	 */
514
	private function resetErrorFromQueue() {
515
		foreach ($this->queuedDeleteIndex as $index) {
516
			$this->indexesRequest->resetError($index);
517
		}
518
	}
519
520
	/**
521
	 *
522
	 */
523
	public function resetErrorsAll() {
524
		$this->indexesRequest->resetAllErrors();
525
	}
526
527
528
	/**
529
	 * @return ExtendedIndex[]
530
	 */
531
	public function getErrorIndexes() {
532
		return $this->indexesRequest->getErrorIndexes();
533
	}
534
535
536
	/**
537
	 * @param string $providerId
538
	 * @param array $documentId
539
	 *
540
	 * @return ExtendedIndex[]
541
	 * @throws IndexDoesNotExistException
542
	 */
543
	public function getIndexes($providerId, $documentId) {
544
		return $this->indexesRequest->getIndexes($providerId, $documentId);
545
	}
546
547
548
	/**
549
	 * @param bool $all
550
	 *
551
	 * @return Index[]
552
	 */
553
	public function getQueuedIndexes($all = false) {
554
		return $this->indexesRequest->getQueuedIndexes($all);
555
	}
556
557
558
	/**
559
	 * @param string $providerId
560
	 *
561
	 * @throws Exception
562
	 */
563
	public function resetIndex($providerId = '') {
564
		$platform = $this->platformService->getPlatform();
565
566
		if ($providerId === '') {
567
			$platform->resetIndex('all');
568
			$this->providerService->setProvidersAsNotIndexed();
569
			$this->indexesRequest->reset();
570
571
			return;
572
		} else {
573
			$providers = [$this->providerService->getProvider($providerId)];
574
		}
575
576
		foreach ($providers AS $provider) {
577
			// TODO: need to specify the map to remove
578
			// TODO: need to remove entries with type=providerId
579
//			$provider->onResettingIndex($platform);
580
581
			$platform->resetIndex($provider->getId());
582
			$this->providerService->setProviderAsIndexed($provider, false);
583
			$this->indexesRequest->deleteFromProviderId($provider->getId());
584
		}
585
	}
586
587
588
}