Completed
Push — master ( 5fa1cf...ca88db )
by Maxence
02:04
created

IndexService::updateRunnerAction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
1
<?php
2
declare(strict_types=1);
3
4
5
/**
6
 * FullTextSearch - Full text search framework for Nextcloud
7
 *
8
 * This file is licensed under the Affero General Public License version 3 or
9
 * later. See the COPYING file.
10
 *
11
 * @author Maxence Lange <[email protected]>
12
 * @copyright 2018
13
 * @license GNU AGPL version 3 or any later version
14
 *
15
 * This program is free software: you can redistribute it and/or modify
16
 * it under the terms of the GNU Affero General Public License as
17
 * published by the Free Software Foundation, either version 3 of the
18
 * License, or (at your option) any later version.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License
26
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
27
 *
28
 */
29
30
31
namespace OCA\FullTextSearch\Service;
32
33
34
use Exception;
35
use OCA\FullTextSearch\Db\IndexesRequest;
36
use OCA\FullTextSearch\Exceptions\DatabaseException;
37
use OCA\FullTextSearch\Exceptions\IndexDoesNotExistException;
38
use OCA\FullTextSearch\Exceptions\NotIndexableDocumentException;
39
use OCA\FullTextSearch\Model\Index;
40
use OCA\FullTextSearch\Model\IndexOptions;
41
use OCA\FullTextSearch\Model\ProviderIndexes;
42
use OCA\FullTextSearch\Model\Runner;
43
use OCP\FullTextSearch\IFullTextSearchPlatform;
44
use OCP\FullTextSearch\IFullTextSearchProvider;
45
use OCP\FullTextSearch\Model\IIndex;
46
use OCP\FullTextSearch\Model\IIndexOptions;
47
use OCP\FullTextSearch\Model\IndexDocument;
48
use OCP\FullTextSearch\Model\IRunner;
49
use OCP\FullTextSearch\Service\IIndexService;
50
51
52
/**
53
 * Class IndexService
54
 *
55
 * @package OCA\FullTextSearch\Service
56
 */
57
class IndexService implements IIndexService {
58
59
60
	/** @var IndexesRequest */
61
	private $indexesRequest;
62
63
	/** @var ConfigService */
64
	private $configService;
65
66
	/** @var ProviderService */
67
	private $providerService;
68
69
	/** @var PlatformService */
70
	private $platformService;
71
72
	/** @var MiscService */
73
	private $miscService;
74
75
76
	/** @var Runner */
77
	private $runner = null;
78
79
	/** @var array */
80
	private $queuedDeleteIndex = [];
81
82
	/** @var int */
83
	private $currentTotalDocuments = 0;
84
85
86
	/**
87
	 * IndexService constructor.
88
	 *
89
	 * @param IndexesRequest $indexesRequest
90
	 * @param ConfigService $configService
91
	 * @param ProviderService $providerService
92
	 * @param PlatformService $platformService
93
	 * @param MiscService $miscService
94
	 */
95 View Code Duplication
	public function __construct(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
96
		IndexesRequest $indexesRequest, ConfigService $configService,
97
		ProviderService $providerService, PlatformService $platformService, MiscService $miscService
98
	) {
99
		$this->indexesRequest = $indexesRequest;
100
		$this->configService = $configService;
101
		$this->providerService = $providerService;
102
		$this->platformService = $platformService;
103
		$this->miscService = $miscService;
104
	}
105
106
107
	/**
108
	 * @param Runner $runner
109
	 */
110
	public function setRunner(Runner $runner) {
111
		$this->runner = $runner;
112
	}
113
114
115
	/**
116
	 * @param string $action
117
	 * @param bool $force
118
	 *
119
	 * @throws Exception
120
	 */
121
	private function updateRunnerAction(string $action, bool $force = false) {
122
		if ($this->runner === null) {
123
			return;
124
		}
125
126
		$this->runner->updateAction($action, $force);
127
	}
128
129
	/**
130
	 * @param string $info
131
	 * @param string $value
132
	 * @param int $color
133
	 */
134
	private function updateRunnerInfo(
135
		string $info, string $value, int $color = IRunner::RESULT_TYPE_SUCCESS
136
	) {
137
		if ($this->runner === null) {
138
			return;
139
		}
140
141
		$this->runner->setInfo($info, $value, $color);
142
	}
143
144
	/**
145
	 * @param array $data
146
	 */
147
	private function updateRunnerInfoArray(array $data) {
148
		if ($this->runner === null) {
149
			return;
150
		}
151
152
		$this->runner->setInfoArray($data);
153
	}
154
155
156
	/**
157
	 * @param IFullTextSearchPlatform $platform
158
	 * @param IFullTextSearchProvider $provider
159
	 * @param string $userId
160
	 * @param IndexOptions $options
161
	 *
162
	 * @throws Exception
163
	 */
164
	public function indexProviderContentFromUser(
165
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, string $userId,
166
		IndexOptions $options
167
	) {
168
		$this->updateRunnerAction('generateIndex' . $provider->getName());
169
		$this->updateRunnerInfoArray(
170
			[
171
				'userId'          => $userId,
172
				'providerId'      => $provider->getId(),
173
				'providerName'    => $provider->getName(),
174
				'documentCurrent' => 0,
175
				'documentTotal'   => 0
176
			]
177
		);
178
179
		$documents = $provider->generateIndexableDocuments($userId);
180
		$this->currentTotalDocuments = sizeof($documents);
181
182
		$this->updateRunnerInfoArray(
183
			[
184
				'documentTotal'   => $this->currentTotalDocuments,
185
				'documentCurrent' => 0
186
			]
187
		);
188
189
		//$maxSize = sizeof($documents);
190
191
		$toIndex = $this->updateDocumentsWithCurrIndex($provider, $documents, $options);
192
		$this->indexDocuments($platform, $provider, $toIndex, $options);
193
	}
194
195
196
	/**
197
	 * @param IFullTextSearchProvider $provider
198
	 * @param IndexDocument[] $documents
199
	 * @param IIndexOptions $options
200
	 *
201
	 * @return IndexDocument[]
202
	 * @throws Exception
203
	 */
204
	private function updateDocumentsWithCurrIndex(
205
		IFullTextSearchProvider $provider, array $documents, IIndexOptions $options
206
	): array {
207
		$currIndex = $this->getProviderIndexFromProvider($provider->getId());
208
		$result = [];
209
		$count = 0;
210
		foreach ($documents as $document) {
211
212
			if ($count % 1000 === 0) {
213
				$this->updateRunnerAction('compareWithCurrentIndex', true);
214
				$this->updateRunnerInfo('documentCurrent', (string)$count);
215
			}
216
			$count++;
217
218
			try {
219
				$index = $currIndex->getIndex($document->getId());
220
			} catch (IndexDoesNotExistException $e) {
221
				$index = new Index($document->getProviderId(), $document->getId());
222
				$index->setStatus(Index::INDEX_FULL);
223
				$index->setLastIndex();
224
			}
225
226
			if ($options->getOption('errors', '') !== 'ignore' && $index->getErrorCount() > 0) {
227
				continue;
228
			}
229
230
			if ($options->getOptionBool('force', false) === true) {
231
				$index->setStatus(Index::INDEX_FULL);
232
			}
233
234
			$index->resetErrors();
235
			$document->setIndex($index);
236
			if ($options->getOptionBool('force', false) === true
237
				|| !$this->isDocumentUpToDate($provider, $document)) {
238
				$result[] = $document;
239
			}
240
		}
241
242
		return $result;
243
	}
244
245
246
	/**
247
	 * @param IFullTextSearchProvider $provider
248
	 * @param IndexDocument $document
249
	 *
250
	 * @return bool
251
	 */
252
	private function isDocumentUpToDate(IFullTextSearchProvider $provider, IndexDocument $document
253
	): bool {
254
		$index = $document->getIndex();
255
256
		if (!$index->isStatus(Index::INDEX_OK)) {
257
			return false;
258
		}
259
260
		if ($index->isStatus(Index::INDEX_META) || $index->isStatus(Index::INDEX_CONTENT)) {
261
			return false;
262
		}
263
264
		return $provider->isDocumentUpToDate($document);
265
	}
266
267
268
	/**
269
	 * @param string $providerId
270
	 *
271
	 * @return ProviderIndexes
272
	 */
273
	private function getProviderIndexFromProvider(string $providerId): ProviderIndexes {
274
		$indexes = $this->indexesRequest->getIndexesFromProvider($providerId);
275
276
		return new ProviderIndexes($indexes);
277
	}
278
279
280
	/**
281
	 * @param IFullTextSearchPlatform $platform
282
	 * @param IFullTextSearchProvider $provider
283
	 * @param IndexDocument[] $documents
284
	 * @param IndexOptions $options
285
	 *
286
	 * @throws Exception
287
	 */
288
	private function indexDocuments(
289
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, array $documents,
290
		IndexOptions $options
291
	) {
292
		while ($document = array_shift($documents)) {
293
			try {
294
295
				$this->updateRunnerInfoArray(
296
					[
297
						'documentCurrent' => ($this->currentTotalDocuments - sizeof($documents) - 1)
298
					]
299
				);
300
				$this->updateRunnerAction('fillDocument', true);
301
				$this->updateRunnerInfoArray(
302
					[
303
						'documentId'    => $document->getId(),
304
						'info'          => '',
305
						'title'         => '',
306
						'content'       => '',
307
						'status'        => '',
308
						'statusColored' => ''
309
					]
310
				);
311
312
				$provider->fillIndexDocument($document);
313
				$this->updateRunnerInfoArray(
314
					[
315
						'title'   => $document->getTitle(),
316
						'content' => $document->getContentSize()
317
					]
318
				);
319
				$this->filterDocumentBeforeIndex($document);
320
321
				$index = $this->indexDocument($platform, $document);
322
				$this->updateIndex($index);
323
324
			} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
325
			}
326
327
			$document->__destruct();
328
			unset($document);
329
		}
330
	}
331
332
333
	/**
334
	 * @param IndexDocument $document
335
	 *
336
	 * @throws NotIndexableDocumentException
337
	 */
338
	private function filterDocumentBeforeIndex(IndexDocument $document) {
339
		// TODO - rework the index/not_index
340
		$index = $document->getIndex();
341
		$access = $document->getAccess();
342
343
// INDEX_IGNORE is not used anymore, as we use addError()
344
		if ($access === null || $index->isStatus(Index::INDEX_IGNORE)) {
345
			throw new NotIndexableDocumentException();
346
		}
347
348
		$index->setOwnerId($access->getOwnerId());
349
	}
350
351
352
	/**
353
	 * @param IFullTextSearchPlatform $platform
354
	 * @param IndexDocument $document
355
	 *
356
	 * @return IIndex
357
	 * @throws Exception
358
	 */
359
	public function indexDocument(IFullTextSearchPlatform $platform, IndexDocument $document
360
	): IIndex {
361
		$this->updateRunnerAction('indexDocument', true);
362
		$this->updateRunnerInfoArray(
363
			[
364
				'documentId' => $document->getId(),
365
				'title'      => $document->getTitle(),
366
				'content'    => $document->getContentSize()
367
			]
368
		);
369
370
		try {
371
			$index = $platform->indexDocument($document);
372
373
			return $index;
374
		} catch (Exception $e) {
375
			throw new IndexDoesNotExistException();
376
		}
377
378
	}
379
380
381
	/**
382
	 * @param IFullTextSearchPlatform $platform
383
	 * @param IFullTextSearchProvider $provider
384
	 * @param Index $index
385
	 *
386
	 * @throws Exception
387
	 */
388
	public function updateDocument(
389
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, Index $index
390
	) {
391
		$document = null;
392
		$this->updateRunnerInfoArray(
393
			[
394
				'providerName' => $provider->getName(),
395
				'userId'       => $index->getOwnerId(),
396
			]
397
		);
398
399
		if (!$index->isStatus(Index::INDEX_REMOVE)) {
400
			try {
401
				$document = $provider->updateDocument($index);
402
			} catch (Exception $e) {
403
				/** we do nothing, because we're not sure provider manage the right MissingDocumentException */
404
			}
405
		}
406
407
		if ($document === null) {
408
			$platform->deleteIndexes([$index]);
409
			$this->indexesRequest->deleteIndex($index);
410
411
			return;
412
		}
413
414
		$this->updateRunnerAction('indexDocument', true);
415
		$this->updateRunnerInfoArray(
416
			[
417
				'documentId' => $document->getId(),
418
				'title'      => $document->getTitle(),
419
				'content'    => $document->getContentSize()
420
			]
421
		);
422
423
		$document->getIndex()
424
				 ->resetErrors();
425
		$index = $platform->indexDocument($document);
426
		$this->updateIndex($index);
427
	}
428
429
430
	/**
431
	 * @param Index[] $indexes
432
	 *
433
	 * @throws DatabaseException
434
	 */
435
	public function updateIndexes(array $indexes) {
436
		try {
437
			foreach ($indexes as $index) {
438
				$this->updateIndex($index);
439
			}
440
			$this->resetErrorFromQueue();
441
		} catch (Exception $e) {
442
			throw new DatabaseException($e->getMessage());
443
		}
444
	}
445
446
447
	/**
448
	 * @param IIndex $index
449
	 *
450
	 * @throws Exception
451
	 */
452
	private function updateIndex(IIndex $index) {
453
454
		/** @var Index $index */
455
		$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...
456
		if ($index->isStatus(IIndex::INDEX_REMOVE)) {
457
458
			if ($index->isStatus(IIndex::INDEX_DONE)) {
459
				$this->indexesRequest->deleteIndex($index);
460
461
				return;
462
			}
463
464
			$this->indexesRequest->update($index);
465
466
			return;
467
		}
468
469
		if ($index->isStatus(IIndex::INDEX_DONE)) {
470
			$index->setStatus(IIndex::INDEX_OK, true);
471
		}
472
473
		if (!$this->indexesRequest->update($index)) {
474
			$this->indexesRequest->create($index);
475
		}
476
	}
477
478
479
	/**
480
	 * @param IIndex $index
481
	 */
482
	private function updateIndexError(IIndex $index) {
483
484
	}
485
486
487
	/**
488
	 * @param string $providerId
489
	 * @param string $documentId
490
	 * @param int $status
491
	 * @param bool $reset
492
	 *
493
	 * @throws Exception
494
	 */
495
	public function updateIndexStatus(
496
		string $providerId, string $documentId, int $status, bool $reset = false
497
	) {
498
		if ($reset === true) {
499
			$this->indexesRequest->updateStatus($providerId, $documentId, $status);
500
501
			return;
502
		}
503
504
		try {
505
			$curr = $this->getIndex($providerId, $documentId);
506
		} catch (IndexDoesNotExistException $e) {
507
			return;
508
		}
509
510
		$curr->setStatus($status);
511
		$this->updateIndex($curr);
512
	}
513
514
515
	/**
516
	 * @param string $providerId
517
	 * @param array $documentIds
518
	 * @param int $status
519
	 * @param bool $reset
520
	 *
521
	 * @throws DatabaseException
522
	 */
523
	public function updateIndexesStatus(
524
		string $providerId, array $documentIds, int $status, bool $reset = false
525
	) {
526
		if ($reset === true) {
527
			$this->indexesRequest->updateStatuses($providerId, $documentIds, $status);
528
529
			return;
530
		}
531
532
		try {
533
			$all = $this->getIndexes($providerId, $documentIds);
534
		} catch (IndexDoesNotExistException $e) {
535
			return;
536
		}
537
538
		foreach ($all as $curr) {
539
			$curr->setStatus($status);
540
			$this->updateIndexes([$curr]);
541
		}
542
	}
543
544
545
	/**
546
	 * @param Index $index
547
	 */
548
	public function resetErrorFromIndex(Index $index) {
549
		if (!$this->indexesRequest->resetError($index)) {
550
			$this->queuedDeleteIndex[] = $index;
551
		}
552
	}
553
554
555
	/**
556
	 *
557
	 */
558
	private function resetErrorFromQueue() {
559
		foreach ($this->queuedDeleteIndex as $index) {
560
			$this->indexesRequest->resetError($index);
561
		}
562
	}
563
564
	/**
565
	 *
566
	 */
567
	public function resetErrorsAll() {
568
		$this->indexesRequest->resetAllErrors();
569
	}
570
571
572
	/**
573
	 * @return Index[]
574
	 */
575
	public function getErrorIndexes(): array {
576
		return $this->indexesRequest->getErrorIndexes();
577
	}
578
579
580
	/**
581
	 * @param string $providerId
582
	 * @param array $documentId
583
	 *
584
	 * @return Index[]
585
	 * @throws IndexDoesNotExistException
586
	 */
587
	public function getIndexes($providerId, $documentId) {
588
		return $this->indexesRequest->getIndexes($providerId, $documentId);
589
	}
590
591
592
	/**
593
	 * @param bool $all
594
	 *
595
	 * @return Index[]
596
	 */
597
	public function getQueuedIndexes(bool $all = false): array {
598
		return $this->indexesRequest->getQueuedIndexes($all);
599
	}
600
601
602
	/**
603
	 * @param string $providerId
604
	 *
605
	 * @throws Exception
606
	 */
607
	public function resetIndex(string $providerId = '') {
608
		$wrapper = $this->platformService->getPlatform();
609
		$platform = $wrapper->getPlatform();
610
611
		if ($providerId === '') {
612
			$platform->resetIndex('all');
613
			$this->providerService->setProvidersAsNotIndexed();
614
			$this->indexesRequest->reset();
615
616
			return;
617
		} else {
618
			$providerWrapper = $this->providerService->getProvider($providerId);
619
			$providers = [$providerWrapper->getProvider()];
620
		}
621
622
		foreach ($providers AS $provider) {
623
			// TODO: need to specify the map to remove
624
			// TODO: need to remove entries with type=providerId
625
//			$provider->onResettingIndex($platform);
626
627
			$platform->resetIndex($provider->getId());
628
			$this->providerService->setProviderAsIndexed($provider, false);
629
			$this->indexesRequest->deleteFromProviderId($provider->getId());
630
		}
631
	}
632
633
634
	/**
635
	 * @param string $providerId
636
	 * @param string $documentId
637
	 *
638
	 * @return IIndex
639
	 * @throws IndexDoesNotExistException
640
	 */
641
	public function getIndex(string $providerId, string $documentId): IIndex {
642
		return $this->indexesRequest->getIndex($providerId, $documentId);
643
	}
644
645
646
	/**
647
	 * @param string $providerId
648
	 * @param string $documentId
649
	 * @param string $userId
650
	 * @param int $status
651
	 *
652
	 * @return IIndex
653
	 * @throws Exception
654
	 */
655
	public function createIndex(string $providerId, string $documentId, string $userId, int $status
656
	): IIndex {
657
		$index = new Index($providerId, $documentId);
658
		$index->setOwnerId($userId);
659
		$index->setStatus($status);
660
661
		$this->indexesRequest->create($index);
662
663
		return $index;
664
	}
665
666
}
667