Completed
Push — master ( a7e9d9...7ea18f )
by Maxence
03:41 queued 01:32
created

IndexService::setRunner()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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