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

Live::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 5
rs 10
cc 1
nc 1
nop 0
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\Command;
28
29
use Exception;
30
use OCA\FullTextSearch\Exceptions\InterruptException;
31
use OCA\FullTextSearch\Exceptions\TickDoesNotExistException;
32
use OCA\FullTextSearch\Model\ExtendedBase;
33
use OCA\FullTextSearch\Model\Runner;
34
use OCA\FullTextSearch\Model\Index as ModelIndex;
35
use OCA\FullTextSearch\Service\CliService;
36
use OCA\FullTextSearch\Service\ConfigService;
37
use OCA\FullTextSearch\Service\IndexService;
38
use OCA\FullTextSearch\Service\MiscService;
39
use OCA\FullTextSearch\Service\PlatformService;
40
use OCA\FullTextSearch\Service\ProviderService;
41
use OCA\FullTextSearch\Service\RunningService;
42
use OCP\IUserManager;
43
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
44
use Symfony\Component\Console\Input\InputInterface;
45
use Symfony\Component\Console\Output\OutputInterface;
46
47
48
class Live extends ExtendedBase {
49
50
	const CYCLE_DELAY = 10;
51
52
	const PANEL_RUN = 'run';
53
	const PANEL_RUN_LINE_MEMORY = 'Memory: %_memory%';
54
55
	const PANEL_INDEX = 'indexing';
56
	const PANEL_INDEX_LINE_HEADER = '┌─ Indexing %_paused% ────';
57
	const PANEL_INDEX_LINE_ACCOUNT = '│ Provider: <info>%providerName:-20s%</info> Account: <info>%userId%</info>';
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 112 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
58
	const PANEL_INDEX_LINE_ACTION = '│ Action: <info>%action%</info>';
59
	const PANEL_INDEX_LINE_DOCUMENT = '│ Document: <info>%documentId%</info>';
60
	const PANEL_INDEX_LINE_INFO = '│ Info: <info>%info%</info>';
61
	const PANEL_INDEX_LINE_TITLE = '│ Title: <info>%title%</info>';
62
	const PANEL_INDEX_LINE_CONTENT = '│ Content size: <info>%content%</info>';
63
	const PANEL_INDEX_LINE_RESULT = '│ Result: %resultColored%';
64
	const PANEL_INDEX_LINE_FOOTER = '└──';
65
66
	const PANEL_STATUS = 'status';
67
	const PANEL_STATUS_LINE_HEADER = '┌─ Status ────';
68
	const PANEL_STATUS_LINE_DOCUMENTS = '│ Progress: %documentLeft:6s%/%documentTotal%   %progressStatus%';
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 104 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
69
//	const PANEL_STATUS_LINE_DOCUMENTS_LEFT = '│ Document left:';
70
	const PANEL_STATUS_LINE_ERRORS = '│ Error: <comment>%errorCurrent:6s%</comment>/<comment>%errorTotal%</comment>';
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 114 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
71
	const PANEL_STATUS_LINE_ERROR_MESSAGE = '│ Message: <comment>%errorMessage%</comment>';
72
	const PANEL_STATUS_LINE_ERROR_EXCEPTION = '│ Exception: <comment>%errorException%</comment>';
73
	const PANEL_STATUS_LINE_ERROR_INDEX = '│ Index: <comment>%errorIndex%</comment>';
74
75
76
	const PANEL_STATUS_LINE_FOOTER = '└──';
77
78
	const PANEL_LINE_EMPTY = '│ ';
79
80
	const PANEL_COMMANDS_ROOT = 'root';
81
	const PANEL_COMMANDS_ROOT_LINE = '## <char>q</char>:quit ## <char>p</char>:pause ';
82
	const PANEL_COMMANDS_PAUSED = 'paused';
83
	const PANEL_COMMANDS_PAUSED_LINE = '## <char>q</char>:quit ## <char>u</char>:unpause ## <char>n</char>:next step';
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 115 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
84
	const PANEL_COMMANDS_DONE = 'done';
85
	const PANEL_COMMANDS_DONE_LINE = '## <char>q</char>:quit';
86
	const PANEL_COMMANDS_ERRORS = 'errors';
87
	const PANEL_COMMANDS_ERRORS_LINE = '## <char>f</char>:first error ## <char>h</char>/<char>j</char>:prec/next error ## <char>d</char>:delete error ## <char>l</char>:last error';
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 177 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
88
89
90
	/** @var IUserManager */
91
	private $userManager;
92
93
	/** @var ConfigService */
94
	private $configService;
95
96
	/** @var CliService */
97
	private $cliService;
98
99
	/** @var RunningService */
100
	private $runningService;
101
102
	/** @var IndexService */
103
	private $indexService;
104
105
	/** @var PlatformService */
106
	private $platformService;
107
108
	/** @var ProviderService */
109
	private $providerService;
110
111
	/** @var MiscService */
112
	private $miscService;
113
114
	/** @var Runner */
115
	private $runner;
116
117
118
	/** @var array */
119
	private $errors = [];
120
121
	/** @var bool */
122
	private $navigateLastError = true;
123
124
	/**
125
	 * Index constructor.
126
	 *
127
	 * @param IUserManager $userManager
128
	 * @param RunningService $runningService
129
	 * @param ConfigService $configService
130
	 * @param IndexService $indexService
131
	 * @param PlatformService $platformService
132
	 * @param ProviderService $providerService
133
	 * @param MiscService $miscService
134
	 */
135
	public function __construct(
136
		IUserManager $userManager, RunningService $runningService, ConfigService $configService,
137
		CliService $cliService, IndexService $indexService, PlatformService $platformService,
138
		ProviderService $providerService, MiscService $miscService
139
	) {
140
		parent::__construct();
141
		$this->userManager = $userManager;
142
143
		$this->runner = new Runner($runningService, 'commandLive');
144
		$this->configService = $configService;
145
		$this->cliService = $cliService;
146
		$this->runningService = $runningService;
147
		$this->indexService = $indexService;
148
		$this->platformService = $platformService;
149
		$this->providerService = $providerService;
150
		$this->miscService = $miscService;
151
	}
152
153
154
	/**
155
	 *
156
	 */
157
	protected function configure() {
158
		parent::configure();
159
		$this->setName('fulltextsearch:live')
160
			 ->setDescription('Index files');
161
	}
162
163
164
	/**
165
	 * @param InputInterface $input
166
	 * @param OutputInterface $output
167
	 *
168
	 * @return int|null|void
169
	 * @throws Exception
170
	 */
171
	protected function execute(InputInterface $input, OutputInterface $output) {
172
173
		if ($this->configService->getCloudVersion() < 14) {
174
			throw new Exception('This feature is only available on Nextcloud 14 or newer');
175
		}
176
177
		/** do not get stuck while waiting interactive input */
178
		readline_callback_handler_install(
179
			'', function() {
180
		}
181
		);
182
		stream_set_blocking(STDIN, false);
183
184
		$outputStyle = new OutputFormatterStyle('white', 'black', ['bold']);
185
		$output->getFormatter()
186
			   ->setStyle('char', $outputStyle);
187
188
		$this->runner = new Runner($this->runningService, 'commandIndex', ['nextStep' => 'n']);
189
		$this->runner->onKeyPress([$this, 'onKeyPressed']);
190
		$this->runner->onNewAction([$this, 'onNewAction']);
191
		$this->runner->onNewIndexError([$this, 'onNewIndexError']);
192
193
		$this->indexService->setRunner($this->runner);
194
		$this->cliService->setRunner($this->runner);
195
196
		$this->generatePanels();
197
198
199
		try {
200
			$this->runner->sourceIsCommandLine($this, $output);
201
			$this->runner->start();
202
203
			$this->cliService->runDisplay($output);
204
			$this->generateIndexErrors();
205
			$this->displayError();
206
207
			$this->liveCycle();
208
209
		} catch (Exception $e) {
210
			$this->runner->exception($e->getMessage(), true);
0 ignored issues
show
Deprecated Code introduced by
The method OCA\FullTextSearch\Model\Runner::exception() has been deprecated with message: - verifier l'interet !?

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
211
			throw $e;
212
		}
213
214
		$this->runner->stop();
215
	}
216
217
218
	/**
219
	 * @throws Exception
220
	 * @throws InterruptException
221
	 * @throws TickDoesNotExistException
222
	 */
223
	private function liveCycle() {
224
225
		$platform = $this->platformService->getPlatform();
226
		$platform->setRunner($this->runner);
227
228
		while (true) {
229
230
			$indexes = $this->indexService->getQueuedIndexes();
231
232 View Code Duplication
			foreach ($indexes as $index) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
233
				$this->runner->updateAction('indexing');
234
235
				try {
236
					$provider = $this->providerService->getProvider($index->getProviderId());
237
					$provider->setRunner($this->runner);
238
					$this->indexService->updateDocument($platform, $provider, $index);
239
				} catch (Exception $e) {
240
					$this->runner->exception($e->getMessage(), false);
0 ignored issues
show
Deprecated Code introduced by
The method OCA\FullTextSearch\Model\Runner::exception() has been deprecated with message: - verifier l'interet !?

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
241
					// TODO - upgrade error number - after too many errors, delete index
242
					// TODO - do not count error if elasticsearch is down.
243
				}
244
			}
245
246
			$this->runner->updateAction('waiting', true);
247
248
			usleep(300000);
249
		}
250
251
		$this->runner->stop();
252
253
	}
254
255
256
	/**
257
	 * @param $key
258
	 */
259 View Code Duplication
	public function onKeyPressed($key) {
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...
260
		$key = strtolower($key);
261
		if ($key === 'q') {
262
			try {
263
				$this->runner->stop();
264
			} catch (TickDoesNotExistException $e) {
265
				/** we do nohtin' */
266
			}
267
			exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method onKeyPressed() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
268
		}
269
270
		$current = $this->cliService->currentPanel('commands');
271
		if ($current === self::PANEL_COMMANDS_ROOT && $key === 'p') {
272
			$this->cliService->switchPanel('commands', self::PANEL_COMMANDS_PAUSED);
273
			$this->runner->pause(true);
274
		}
275
		if ($current === self::PANEL_COMMANDS_PAUSED && $key === 'u') {
276
			$this->cliService->switchPanel('commands', self::PANEL_COMMANDS_ROOT);
277
			$this->runner->pause(false);
278
		}
279
280
		if ($key === 'f') {
281
			$this->displayError(-99);
282
		}
283
		if ($key === 'h') {
284
			$this->displayError(-1);
285
		}
286
		if ($key === 'j') {
287
			$this->displayError(1);
288
		}
289
		if ($key === 'l') {
290
			$this->displayError(99);
291
		}
292
		if ($key === 'd') {
293
			$this->deleteError();
294
		}
295
	}
296
297
298
	/**
299
	 * @param array $error
300
	 */
301
	public function onNewIndexError($error) {
302
		$this->errors[] = $error;
303
		$this->displayError();
304
	}
305
306
307
	/**
308
	 *
309
	 */
310 View Code Duplication
	private function generatePanels() {
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...
311
312
		$this->cliService->createPanel(
313
			self::PANEL_RUN,
314
			[
315
				self::PANEL_RUN_LINE_MEMORY
316
			]
317
		);
318
		$this->cliService->createPanel(
319
			self::PANEL_INDEX, [
320
								 self::PANEL_INDEX_LINE_HEADER,
321
								 self::PANEL_INDEX_LINE_ACCOUNT,
322
								 self::PANEL_INDEX_LINE_ACTION,
323
								 self::PANEL_INDEX_LINE_DOCUMENT,
324
								 self::PANEL_INDEX_LINE_INFO,
325
								 self::PANEL_INDEX_LINE_TITLE,
326
								 self::PANEL_INDEX_LINE_CONTENT,
327
								 self::PANEL_INDEX_LINE_RESULT,
328
								 self::PANEL_INDEX_LINE_FOOTER,
329
							 ]
330
		);
331
332
		$this->cliService->createPanel(
333
			self::PANEL_STATUS, [
334
								  self::PANEL_STATUS_LINE_HEADER,
335
								  self::PANEL_STATUS_LINE_DOCUMENTS,
336
								  self::PANEL_STATUS_LINE_ERRORS,
337
								  self::PANEL_STATUS_LINE_ERROR_MESSAGE,
338
								  self::PANEL_STATUS_LINE_ERROR_EXCEPTION,
339
								  self::PANEL_STATUS_LINE_ERROR_INDEX,
340
								  self::PANEL_STATUS_LINE_FOOTER,
341
							  ]
342
		);
343
344
		$this->cliService->createPanel(
345
			self::PANEL_COMMANDS_ROOT, [
346
										 self::PANEL_COMMANDS_ROOT_LINE
347
									 ]
348
		);
349
350
		$this->cliService->createPanel(
351
			self::PANEL_COMMANDS_PAUSED, [
352
										   self::PANEL_COMMANDS_PAUSED_LINE
353
									   ]
354
		);
355
356
		$this->cliService->createPanel(
357
			self::PANEL_COMMANDS_ERRORS, [
358
										   self::PANEL_COMMANDS_ERRORS_LINE
359
									   ]
360
		);
361
362
		$this->cliService->initDisplay();
363
		$this->cliService->displayPanel('run', self::PANEL_RUN);
364
		$this->cliService->displayPanel('topPanel', self::PANEL_INDEX);
365
		$this->cliService->displayPanel('bottomPanel', self::PANEL_STATUS);
366
367
		$this->cliService->displayPanel('errors', self::PANEL_COMMANDS_ERRORS);
368
369
		if ($this->runner->isPaused()) {
370
			$this->cliService->displayPanel('commands', self::PANEL_COMMANDS_PAUSED);
371
		} else {
372
			$this->cliService->displayPanel('commands', self::PANEL_COMMANDS_ROOT);
373
		}
374
375
		$this->runner->setInfoArray(
376
			[
377
				'userId'         => '',
378
				'providerName'   => '',
379
				'_memory'        => '',
380
				'documentId'     => '',
381
				'action'         => '',
382
				'info'           => '',
383
				'title'          => '',
384
				'_paused'        => '',
385
				'content'        => '',
386
				'resultColored'  => '',
387
				'documentLeft'   => '',
388
				'documentTotal'  => '',
389
				'progressStatus' => '',
390
				'errorCurrent'   => '0',
391
				'errorTotal'     => '0',
392
				'errorMessage'   => '',
393
				'errorException' => '',
394
				'errorIndex'     => ''
395
			]
396
		);
397
	}
398
399
400
	/**
401
	 *
402
	 */
403 View Code Duplication
	private function generateIndexErrors() {
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...
404
		$indexes = $this->indexService->getErrorIndexes();
405
406
		foreach ($indexes as $index) {
407
			foreach ($index->getErrors() as $error) {
408
				$this->errors[] = [
409
					'index'     => $index,
410
					'message'   => $error['message'],
411
					'exception' => $error['exception'],
412
					'severity'  => $error['sev']
413
				];
414
			}
415
416
		}
417
418
419
	}
420
421
422
	/**
423
	 * @param int $pos
424
	 */
425 View Code Duplication
	private function displayError($pos = 0) {
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...
426
		$total = sizeof($this->errors);
427
428
		if ($total === 0) {
429
			$this->runner->setInfoArray(
430
				[
431
					'errorCurrent' => 0,
432
					'errorTotal'   => 0,
433
				]
434
			);
435
436
			return;
437
		}
438
439
		$current = key($this->errors) + 1;
440
		$error = $this->getNavigationError($pos, ($current === 1), ($current === $total));
441
		$current = key($this->errors) + 1;
442
443
		if ($error === false) {
444
			return;
445
		}
446
447
		/** @var ModelIndex $index */
448
		$index = $error['index'];
449
		$errorIndex = '';
450
		if ($index !== null) {
451
			$errorIndex = $index->getProviderId() . ':' . $index->getDocumentId();
452
		}
453
454
		$this->runner->setInfoArray(
455
			[
456
				'errorCurrent'   => $current,
457
				'errorTotal'     => $total,
458
				'errorMessage'   => MiscService::get('message', $error, ''),
459
				'errorException' => MiscService::get('exception', $error, ''),
460
				'errorIndex'     => $errorIndex
461
			]
462
		);
463
	}
464
465
466
	/**
467
	 * @param int $pos
468
	 * @param bool $isFirst
469
	 * @param bool $isLast
470
	 *
471
	 * @return bool|array
472
	 */
473 View Code Duplication
	private function getNavigationError($pos, $isFirst, $isLast) {
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...
474
475
		if ($pos === 0) {
476
			if ($this->navigateLastError === true) {
477
				return end($this->errors);
478
			} else {
479
				return current($this->errors);
480
			}
481
		}
482
483
		$this->navigateLastError = false;
484
		if ($pos === -99) {
485
			return reset($this->errors);
486
		}
487
488
		if ($pos === -1 && !$isFirst) {
489
			return prev($this->errors);
490
		}
491
492
		if ($pos === 1 && !$isLast) {
493
			return next($this->errors);
494
		}
495
496
		if ($pos === 99) {
497
			$this->navigateLastError = true;
498
499
			return end($this->errors);
500
		}
501
502
		return false;
503
	}
504
505
506
	/**
507
	 *
508
	 */
509 View Code Duplication
	private function deleteError() {
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...
510
		$current = current($this->errors);
511
		if ($current === false) {
512
			return;
513
		}
514
515
		$this->runner->setInfoArray(
516
			[
517
				'errorMessage'   => '',
518
				'errorException' => '',
519
				'errorIndex'     => ''
520
			]
521
		);
522
523
		$pos = key($this->errors);
524
525
		/** @var ModelIndex $index */
526
		$index = $current['index'];
527
		$this->indexService->resetErrorFromIndex($index);
528
529
		$errors = [];
530
		foreach ($this->errors as $error) {
531
			/** @var ModelIndex $errorIndex */
532
			$errorIndex = $error['index'];
533
			if ($index->getProviderId() === $errorIndex->getProviderId()
534
				&& $index->getDocumentId() === $errorIndex->getDocumentId()) {
535
				continue;
536
			}
537
538
			$errors[] = $error;
539
		}
540
541
		$this->errors = $errors;
542
		while (key($this->errors) < $pos) {
543
			if (next($this->errors) === false) {
544
				end($this->errors);
545
				break;
546
			}
547
		}
548
549
		$this->displayError();
550
	}
551
}
552
553
554
555