Completed
Push — master ( f0b8b1...988dc2 )
by Maxence
02:48 queued 57s
created

IndexService::updateRunnerInfo()   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 3
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\TickDoesNotExistException;
36
use OCA\FullTextSearch\IFullTextSearchPlatform;
37
use OCA\FullTextSearch\IFullTextSearchProvider;
38
use OCA\FullTextSearch\Model\ExtendedIndex;
39
use OCA\FullTextSearch\Model\Index;
40
use OCA\FullTextSearch\Model\IndexDocument;
41
use OCA\FullTextSearch\Model\IndexOptions;
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
	/** @var array */
67
	private $queuedDeleteIndex = [];
68
69
70
	/**
71
	 * IndexService constructor.
72
	 *
73
	 * @param IndexesRequest $indexesRequest
74
	 * @param ConfigService $configService
75
	 * @param ProviderService $providerService
76
	 * @param PlatformService $platformService
77
	 * @param MiscService $miscService
78
	 */
79
	public function __construct(
80
		IndexesRequest $indexesRequest, ConfigService $configService,
81
		ProviderService $providerService,
82
		PlatformService $platformService, MiscService $miscService
83
	) {
84
		$this->indexesRequest = $indexesRequest;
85
		$this->configService = $configService;
86
		$this->providerService = $providerService;
87
		$this->platformService = $platformService;
88
		$this->miscService = $miscService;
89
	}
90
91
92
	/**
93
	 * @param Runner $runner
94
	 */
95
	public function setRunner(Runner $runner) {
96
		$this->runner = $runner;
97
	}
98
99
100
	/**
101
	 * @param string $action
102
	 * @param bool $force
103
	 *
104
	 * @throws InterruptException
105
	 * @throws TickDoesNotExistException
106
	 */
107
	private function updateRunnerAction($action, $force = false) {
108
		if ($this->runner === null) {
109
			return;
110
		}
111
112
		$this->runner->updateAction($action, $force);
113
	}
114
115
	/**
116
	 * @param string $info
117
	 * @param string $value
118
	 */
119
	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...
120
		if ($this->runner === null) {
121
			return;
122
		}
123
124
		$this->runner->setInfo($info, $value, $color);
125
	}
126
127
	/**
128
	 * @param array $data
129
	 */
130
	private function updateRunnerInfoArray($data) {
131
		if ($this->runner === null) {
132
			return;
133
		}
134
135
		$this->runner->setInfoArray($data);
136
	}
137
138
139
	/**
140
	 * @param IFullTextSearchPlatform $platform
141
	 * @param IFullTextSearchProvider $provider
142
	 * @param string $userId
143
	 * @param IndexOptions $options
144
	 *
145
	 * @throws InterruptException
146
	 * @throws TickDoesNotExistException
147
	 * @throws Exception
148
	 */
149
	public function indexProviderContentFromUser(
150
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, $userId, $options
151
	) {
152
		$this->updateRunnerAction('generateIndex' . $provider->getName());
153
		$this->updateRunnerInfoArray(
154
			[
155
				'userId'       => $userId,
156
				'providerId'   => $provider->getId(),
157
				'providerName' => $provider->getName()
158
			]
159
		);
160
161
		$documents = $provider->generateIndexableDocuments($userId);
162
163
		$this->updateRunnerInfoArray(
164
			[
165
				'documentTotal' => sizeof($documents),
166
				'documentLeft'  => ''
167
			]
168
		);
169
170
		//$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...
171
172
		$toIndex = $this->updateDocumentsWithCurrIndex($provider, $documents, $options);
173
		$this->indexChunks($platform, $provider, $toIndex, $options);
174
175
//		$this->updateRunnerInfoArray(
0 ignored issues
show
Unused Code Comprehensibility introduced by
46% 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
//				'documentLeft' => 0
178
//			]
179
//		);
180
	}
181
182
183
	/**
184
	 * @param IFullTextSearchProvider $provider
185
	 * @param IndexDocument[] $documents
186
	 * @param IndexOptions $options
187
	 *
188
	 * @return IndexDocument[]
189
	 * @throws InterruptException
190
	 * @throws TickDoesNotExistException
191
	 */
192
	private function updateDocumentsWithCurrIndex(
193
		IFullTextSearchProvider $provider, array $documents, IndexOptions $options
194
	) {
195
196
		$currIndex = $this->getProviderIndexFromProvider($provider);
197
		$result = [];
198
		foreach ($documents as $document) {
199
			$this->updateRunnerAction('compareWithCurrentIndex');
200
201
			$index = $currIndex->getIndex($document->getId());
202
			if ($index === null) {
203
				$index = new Index($document->getProviderId(), $document->getId());
204
				$index->setStatus(Index::INDEX_FULL);
205
				$index->setLastIndex();
206
			}
207
208
			if ($options->getOption('errors', '') !== 'ignore' && $index->getErrorCount() > 0) {
209
				continue;
210
			}
211
212
			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...
213
				$index->setStatus(Index::INDEX_FULL);
214
			}
215
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 indexChunks(
272
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, $documents,
273
		IndexOptions $options
274
	) {
275
		$chunkSize = $options->getOption(
276
			'chunk', $this->configService->getAppValue(ConfigService::CHUNK_INDEX)
277
		);
278
279
		$max = sizeof($documents);
280
		for ($i = 0; $i < $max; $i++) {
281
			$this->updateRunnerAction('indexChunk', true);
282
			try {
283
284
				$this->updateRunnerInfoArray(['documentLeft' => sizeof($documents)]);
285
286
				$chunk = array_splice($documents, 0, $chunkSize);
287
				$this->indexChunk($platform, $provider, $chunk);
288
289
				/** @var IndexDocument $doc */
290
				foreach ($chunk as $doc) {
291
					$doc->__destruct(); // because.
292
				}
293
			} catch (NoResultException $e) {
294
				$this->updateRunnerInfoArray(['documentLeft' => 0]);
295
296
				return;
297
			} catch (Exception $e) {
298
				throw $e;
299
			}
300
		}
301
302
		$this->updateRunnerAction('indexChunk', true);
303
	}
304
305
306
	/**
307
	 * @param IFullTextSearchPlatform $platform
308
	 * @param IFullTextSearchProvider $provider
309
	 * @param IndexDocument[] $chunk
310
	 *
311
	 * @throws NoResultException
312
	 * @throws DatabaseException
313
	 * @throws Exception
314
	 */
315
	private function indexChunk(
316
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, $chunk
317
	) {
318
		if (sizeof($chunk) === 0) {
319
			throw new NoResultException();
320
		}
321
322
		$documents = $provider->fillIndexDocuments($chunk);
323
		$toIndex = $this->filterDocumentsToIndex($documents);
324
325
		$indexes = [];
326
		foreach ($toIndex as $document) {
327
			try {
328
				$index = $this->indexDocument($platform, $provider, $document);
329
				$indexes[] = $index;
330
			} catch (\Exception $e) {
331
				throw $e;
332
			}
333
		}
334
335
		$this->updateIndexes($indexes);
336
	}
337
338
339
	/**
340
	 * @param IndexDocument[] $documents
341
	 *
342
	 * @return IndexDocument[]
343
	 */
344
	private function filterDocumentsToIndex($documents) {
345
		$toIndex = [];
346
		foreach ($documents as $document) {
347
			// TODO - rework the index/not_index
348
			$index = $document->getIndex();
349
			$access = $document->getAccess();
350
351
			if ($access !== null && !$index->isStatus(Index::INDEX_IGNORE)) {
352
				$index->setOwnerId($access->getOwnerId());
353
				$toIndex[] = $document;
354
			}
355
		}
356
357
		return $toIndex;
358
	}
359
360
361
	/**
362
	 * @param IFullTextSearchPlatform $platform
363
	 * @param IFullTextSearchProvider $provider
364
	 * @param IndexDocument $document
365
	 *
366
	 * @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...
367
	 * @throws Exception
368
	 */
369
	public function indexDocument(
370
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, $document
371
	) {
372
		$this->updateRunnerAction('indexDocument', true);
373
		$this->updateRunnerInfoArray(
374
			[
375
				'documentId' => $document->getId(),
376
				'title'      => $document->getTitle(),
377
				'content'    => $document->getContentSize()
378
			]
379
		);
380
381
		$index = null;
382
		$document->getIndex()
383
				 ->resetErrors();
384
		try {
385
			$index = $platform->indexDocument($provider, $document);
386
		} catch (Exception $e) {
387
			if ($this->runner->isStrict()) {
388
				throw $e;
389
			}
390
		}
391
392
		return $index;
393
	}
394
395
396
	/**
397
	 * @param IFullTextSearchPlatform $platform
398
	 * @param IFullTextSearchProvider $provider
399
	 * @param Index $index
400
	 *
401
	 * @internal param int|string $documentId
402
	 * @throws Exception
403
	 */
404
	public function updateDocument(
405
		IFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, Index $index
406
	) {
407
		$document = null;
408
		if (!$index->isStatus(Index::INDEX_REMOVE)) {
409
			try {
410
				$document = $provider->updateDocument($index);
411
			} catch (Exception $e) {
412
				/** we do nothing, because we're not sure provider manage the right MissingDocumentException */
413
			}
414
		}
415
416
		if ($document === null) {
417
			$platform->deleteIndexes([$index]);
418
			$this->indexesRequest->deleteIndex($index);
419
420
			return;
421
		}
422
423
		$document->getIndex()
424
				 ->resetErrors();
425
		$index = $platform->indexDocument($provider, $document);
426
		$this->updateIndex($index);
427
	}
428
429
430
	/**
431
	 * @param Index[] $indexes
432
	 *
433
	 * @throws DatabaseException
434
	 */
435
	public function updateIndexes($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 Index $index
449
	 *
450
	 * @throws Exception
451
	 */
452
	private function updateIndex(Index $index) {
453
454
		$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...
455
		if ($index->isStatus(Index::INDEX_REMOVE)) {
456
457
			if ($index->isStatus(Index::INDEX_DONE)) {
458
				$this->indexesRequest->deleteIndex($index);
459
460
				return;
461
			}
462
463
			$this->indexesRequest->update($index);
464
465
			return;
466
		}
467
468
		if ($index->isStatus(Index::INDEX_DONE)) {
469
			$index->setStatus(Index::INDEX_OK, true);
470
		}
471
472
		if (!$this->indexesRequest->update($index)) {
473
			$this->indexesRequest->create($index);
474
		}
475
	}
476
477
478
	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...
479
480
	}
481
482
483
	/**
484
	 * @param string $providerId
485
	 * @param array $documentIds
486
	 * @param int $status
487
	 * @param bool $reset
488
	 *
489
	 * @throws DatabaseException
490
	 */
491
	public function updateIndexesStatus($providerId, $documentIds, $status, $reset = false) {
492
		if ($reset === true) {
493
			$this->indexesRequest->updateStatus($providerId, $documentIds, $status);
494
495
			return;
496
		}
497
498
		try {
499
			$all = $this->getIndexes($providerId, $documentIds);
500
		} catch (IndexDoesNotExistException $e) {
501
			return;
502
		}
503
504
		foreach ($all as $curr) {
505
			$curr->setStatus($status);
506
			$this->updateIndexes([$curr]);
507
		}
508
509
	}
510
511
512
	/**
513
	 * @param Index $index
514
	 */
515
	public function resetErrorFromIndex(Index $index) {
516
		if (!$this->indexesRequest->resetError($index)) {
517
			$this->queuedDeleteIndex[] = $index;
518
		}
519
	}
520
521
522
	/**
523
	 *
524
	 */
525
	private function resetErrorFromQueue() {
526
		foreach ($this->queuedDeleteIndex as $index) {
527
			$this->indexesRequest->resetError($index);
528
		}
529
	}
530
531
	/**
532
	 *
533
	 */
534
	public function resetErrorsAll() {
535
		$this->indexesRequest->resetAllErrors();
536
	}
537
538
539
	/**
540
	 * @return ExtendedIndex[]
541
	 */
542
	public function getErrorIndexes() {
543
		return $this->indexesRequest->getErrorIndexes();
544
	}
545
546
547
	/**
548
	 * @param string $providerId
549
	 * @param array $documentId
550
	 *
551
	 * @return ExtendedIndex[]
552
	 * @throws IndexDoesNotExistException
553
	 */
554
	public function getIndexes($providerId, $documentId) {
555
		return $this->indexesRequest->getIndexes($providerId, $documentId);
556
	}
557
558
559
	/**
560
	 * @param bool $all
561
	 *
562
	 * @return Index[]
563
	 */
564
	public function getQueuedIndexes($all = false) {
565
		return $this->indexesRequest->getQueuedIndexes($all);
566
	}
567
568
569
	/**
570
	 * @param string $providerId
571
	 *
572
	 * @throws Exception
573
	 */
574
	public function resetIndex($providerId = '') {
575
		$platform = $this->platformService->getPlatform();
576
577
		if ($providerId === '') {
578
			$platform->resetIndex('all');
579
			$this->providerService->setProvidersAsNotIndexed();
580
			$this->indexesRequest->reset();
581
582
			return;
583
		} else {
584
			$providers = [$this->providerService->getProvider($providerId)];
585
		}
586
587
		foreach ($providers AS $provider) {
588
			// TODO: need to specify the map to remove
589
			// TODO: need to remove entries with type=providerId
590
//			$provider->onResettingIndex($platform);
591
592
			$platform->resetIndex($provider->getId());
593
			$this->providerService->setProviderAsIndexed($provider, false);
594
			$this->indexesRequest->deleteFromProviderId($provider->getId());
595
		}
596
	}
597
598
599
}