Completed
Push — master ( 347a51...ad246f )
by Maxence
02:23
created

IndexService::resetIndex()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 9.52
c 0
b 0
f 0
cc 3
nc 3
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\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
	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
				'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