| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  | # SPDX-License-Identifier: AGPL-3.0-or-later | 
					
						
							| 
									
										
										
										
											2021-05-05 16:47:02 +02:00
										 |  |  | # lint: pylint | 
					
						
							| 
									
										
										
										
											2021-09-07 13:34:35 +02:00
										 |  |  | # pylint: disable=missing-module-docstring | 
					
						
							| 
									
										
										
										
											2022-01-24 09:46:32 +01:00
										 |  |  | # pyright: strict | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | import json | 
					
						
							|  |  |  | import random | 
					
						
							|  |  |  | import time | 
					
						
							|  |  |  | import threading | 
					
						
							|  |  |  | import os | 
					
						
							|  |  |  | import signal | 
					
						
							| 
									
										
										
										
											2022-01-24 09:46:32 +01:00
										 |  |  | from typing import Dict, Union, List, Any, Tuple | 
					
						
							|  |  |  | from typing_extensions import TypedDict, Literal | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | from searx import logger, settings, searx_debug | 
					
						
							|  |  |  | from searx.exceptions import SearxSettingsException | 
					
						
							| 
									
										
										
										
											2021-05-05 13:08:54 +02:00
										 |  |  | from searx.search.processors import PROCESSORS | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  | from searx.search.checker import Checker | 
					
						
							|  |  |  | from searx.shared import schedule, storage | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | CHECKER_RESULT = 'CHECKER_RESULT' | 
					
						
							|  |  |  | running = threading.Lock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-24 09:46:32 +01:00
										 |  |  | CheckerResult = Union['CheckerOk', 'CheckerErr', 'CheckerOther'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class CheckerOk(TypedDict): | 
					
						
							|  |  |  |     """Checking the engines succeeded""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     status: Literal['ok'] | 
					
						
							|  |  |  |     engines: Dict[str, 'EngineResult'] | 
					
						
							|  |  |  |     timestamp: int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class CheckerErr(TypedDict): | 
					
						
							|  |  |  |     """Checking the engines failed""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     status: Literal['error'] | 
					
						
							|  |  |  |     timestamp: int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class CheckerOther(TypedDict): | 
					
						
							|  |  |  |     """The status is unknown or disabled""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     status: Literal['unknown', 'disabled'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | EngineResult = Union['EngineOk', 'EngineErr'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class EngineOk(TypedDict): | 
					
						
							|  |  |  |     """Checking the engine succeeded""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     success: Literal[True] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class EngineErr(TypedDict): | 
					
						
							|  |  |  |     """Checking the engine failed""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     success: Literal[False] | 
					
						
							|  |  |  |     errors: Dict[str, List[str]] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _get_interval(every: Any, error_msg: str) -> Tuple[int, int]: | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  |     if isinstance(every, int): | 
					
						
							| 
									
										
										
										
											2022-01-24 09:46:32 +01:00
										 |  |  |         return (every, every) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-27 09:26:22 +01:00
										 |  |  |     if ( | 
					
						
							|  |  |  |         not isinstance(every, (tuple, list)) | 
					
						
							| 
									
										
										
										
											2022-01-24 09:46:32 +01:00
										 |  |  |         or len(every) != 2  # type: ignore | 
					
						
							| 
									
										
										
										
											2021-12-27 09:26:22 +01:00
										 |  |  |         or not isinstance(every[0], int) | 
					
						
							|  |  |  |         or not isinstance(every[1], int) | 
					
						
							|  |  |  |     ): | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  |         raise SearxSettingsException(error_msg, None) | 
					
						
							| 
									
										
										
										
											2022-01-24 09:46:32 +01:00
										 |  |  |     return (every[0], every[1]) | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _get_every(): | 
					
						
							|  |  |  |     every = settings.get('checker', {}).get('scheduling', {}).get('every', (300, 1800)) | 
					
						
							|  |  |  |     return _get_interval(every, 'checker.scheduling.every is not a int or list') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-24 09:46:32 +01:00
										 |  |  | def get_result() -> CheckerResult: | 
					
						
							| 
									
										
										
										
											2021-01-11 18:43:12 +01:00
										 |  |  |     serialized_result = storage.get_str(CHECKER_RESULT) | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  |     if serialized_result is not None: | 
					
						
							|  |  |  |         return json.loads(serialized_result) | 
					
						
							| 
									
										
										
										
											2021-08-14 18:22:42 +02:00
										 |  |  |     return {'status': 'unknown'} | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-24 09:46:32 +01:00
										 |  |  | def _set_result(result: CheckerResult): | 
					
						
							| 
									
										
										
										
											2021-01-11 18:43:12 +01:00
										 |  |  |     storage.set_str(CHECKER_RESULT, json.dumps(result)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-24 09:46:32 +01:00
										 |  |  | def _timestamp(): | 
					
						
							|  |  |  |     return int(time.time() / 3600) * 3600 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  | def run(): | 
					
						
							| 
									
										
										
										
											2021-12-27 09:26:22 +01:00
										 |  |  |     if not running.acquire(blocking=False):  # pylint: disable=consider-using-with | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  |         return | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         logger.info('Starting checker') | 
					
						
							| 
									
										
										
										
											2022-01-24 09:46:32 +01:00
										 |  |  |         result: CheckerOk = {'status': 'ok', 'engines': {}, 'timestamp': _timestamp()} | 
					
						
							| 
									
										
										
										
											2021-05-05 13:08:54 +02:00
										 |  |  |         for name, processor in PROCESSORS.items(): | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  |             logger.debug('Checking %s engine', name) | 
					
						
							|  |  |  |             checker = Checker(processor) | 
					
						
							|  |  |  |             checker.run() | 
					
						
							|  |  |  |             if checker.test_results.succesfull: | 
					
						
							| 
									
										
										
										
											2021-01-11 18:43:12 +01:00
										 |  |  |                 result['engines'][name] = {'success': True} | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  |             else: | 
					
						
							| 
									
										
										
										
											2021-01-11 18:43:12 +01:00
										 |  |  |                 result['engines'][name] = {'success': False, 'errors': checker.test_results.errors} | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-11 18:43:12 +01:00
										 |  |  |         _set_result(result) | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  |         logger.info('Check done') | 
					
						
							| 
									
										
										
										
											2021-05-05 16:47:02 +02:00
										 |  |  |     except Exception:  # pylint: disable=broad-except | 
					
						
							| 
									
										
										
										
											2022-01-24 09:46:32 +01:00
										 |  |  |         _set_result({'status': 'error', 'timestamp': _timestamp()}) | 
					
						
							| 
									
										
										
										
											2021-01-11 18:43:12 +01:00
										 |  |  |         logger.exception('Error while running the checker') | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  |     finally: | 
					
						
							|  |  |  |         running.release() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _run_with_delay(): | 
					
						
							|  |  |  |     every = _get_every() | 
					
						
							|  |  |  |     delay = random.randint(0, every[1] - every[0]) | 
					
						
							|  |  |  |     logger.debug('Start checker in %i seconds', delay) | 
					
						
							|  |  |  |     time.sleep(delay) | 
					
						
							|  |  |  |     run() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _start_scheduling(): | 
					
						
							|  |  |  |     every = _get_every() | 
					
						
							| 
									
										
										
										
											2021-01-13 14:07:39 +01:00
										 |  |  |     if schedule(every[0], _run_with_delay): | 
					
						
							|  |  |  |         run() | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-24 09:46:32 +01:00
										 |  |  | def _signal_handler(_signum: int, _frame: Any): | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  |     t = threading.Thread(target=run) | 
					
						
							|  |  |  |     t.daemon = True | 
					
						
							|  |  |  |     t.start() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def initialize(): | 
					
						
							| 
									
										
										
										
											2021-03-14 22:58:59 +01:00
										 |  |  |     if hasattr(signal, 'SIGUSR1'): | 
					
						
							|  |  |  |         # Windows doesn't support SIGUSR1 | 
					
						
							|  |  |  |         logger.info('Send SIGUSR1 signal to pid %i to start the checker', os.getpid()) | 
					
						
							|  |  |  |         signal.signal(signal.SIGUSR1, _signal_handler) | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-11 18:43:12 +01:00
										 |  |  |     # disabled by default | 
					
						
							| 
									
										
										
										
											2022-01-24 09:46:32 +01:00
										 |  |  |     _set_result({'status': 'disabled'}) | 
					
						
							| 
									
										
										
										
											2021-01-11 18:43:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  |     # special case when debug is activate | 
					
						
							|  |  |  |     if searx_debug and settings.get('checker', {}).get('off_when_debug', True): | 
					
						
							|  |  |  |         logger.info('debug mode: checker is disabled') | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # check value of checker.scheduling.every now | 
					
						
							|  |  |  |     scheduling = settings.get('checker', {}).get('scheduling', None) | 
					
						
							|  |  |  |     if scheduling is None or not scheduling: | 
					
						
							|  |  |  |         logger.info('Checker scheduler is disabled') | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # | 
					
						
							| 
									
										
										
										
											2022-01-24 09:46:32 +01:00
										 |  |  |     _set_result({'status': 'unknown'}) | 
					
						
							| 
									
										
										
										
											2021-01-11 18:43:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-05 11:24:39 +01:00
										 |  |  |     start_after = scheduling.get('start_after', (300, 1800)) | 
					
						
							|  |  |  |     start_after = _get_interval(start_after, 'checker.scheduling.start_after is not a int or list') | 
					
						
							|  |  |  |     delay = random.randint(start_after[0], start_after[1]) | 
					
						
							|  |  |  |     logger.info('Start checker in %i seconds', delay) | 
					
						
							|  |  |  |     t = threading.Timer(delay, _start_scheduling) | 
					
						
							|  |  |  |     t.daemon = True | 
					
						
							|  |  |  |     t.start() |