Completed
Push — master ( 1ed105...ebc2fd )
by Sam
02:13
created

TvheadendStatusManagerCommand::configureLogger()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 17
rs 9.4285
cc 2
eloc 10
nc 2
nop 3
1
<?php
2
3
namespace Jalle19\StatusManager\Console\Commands;
4
5
use Bramus\Monolog\Formatter\ColoredLineFormatter;
6
use Jalle19\StatusManager\Configuration;
7
use Jalle19\StatusManager\Exception\InvalidConfigurationException;
8
use Jalle19\StatusManager\Instance;
9
use Jalle19\StatusManager\StatusManager;
10
use Monolog\Handler\StreamHandler;
11
use Monolog\Logger;
12
use Monolog\Processor\PsrLogMessageProcessor;
13
use Propel\Runtime\ServiceContainer\StandardServiceContainer;
14
use Psr\Log\LoggerInterface;
15
use Symfony\Bridge\Monolog\Handler\ConsoleHandler;
16
use Symfony\Component\Console\Command\Command;
17
use Symfony\Component\Console\Input\InputArgument;
18
use Symfony\Component\Console\Input\InputInterface;
19
use Symfony\Component\Console\Input\InputOption;
20
use Symfony\Component\Console\Output\OutputInterface;
21
22
/**
23
 * Class TvheadendStatusManagerCommand
24
 * @package   Jalle19\StatusManager\Console\Command
25
 * @copyright Copyright &copy; Sam Stenvall 2015-
26
 * @license   https://www.gnu.org/licenses/gpl.html The GNU General Public License v2.0
27
 */
28
class TvheadendStatusManagerCommand extends Command
29
{
30
31
	const COMMAND_NAME = 'tvheadend-status-manager';
32
33
34
	/**
35
	 * @inheritdoc
36
	 */
37
	protected function configure()
38
	{
39
		$this->setName(self::COMMAND_NAME);
40
		$this->setDescription('Aggregating status manager for tvheadend instances');
41
42
		// Add arguments
43
		$this->addArgument('configFile', InputArgument::REQUIRED, 'The path to the configuration file');
44
		$this->addArgument('databaseFile', InputArgument::REQUIRED, 'The path to the database');
45
		$this->addArgument('logFile', InputArgument::OPTIONAL, 'The path to the log file');
46
47
		// Add options
48
		$this->addOption('updateInterval', 'i', InputOption::VALUE_REQUIRED, 'The status update interval (in seconds)',
49
			Configuration::DEFAULT_UPDATE_INTERVAL);
50
51
		$this->addOption('listenAddress', 'l', InputOption::VALUE_REQUIRED,
52
			'The address the Websocket server should be listening on',
53
			Configuration::DEFAULT_LISTEN_ADDRESS);
54
55
		$this->addOption('listenPort', 'p', InputOption::VALUE_REQUIRED,
56
			'The port the Websocket server should be listening on', Configuration::DEFAULT_LISTEN_PORT);
57
	}
58
59
60
	/**
61
	 * @inheritdoc
62
	 */
63
	protected function execute(InputInterface $input, OutputInterface $output)
64
	{
65
		// Parse the configuration
66
		$configuration = $this->parseConfiguration($input);
67
68
		// Configure the logger
69
		$logger = $this->configureLogger($input, $output, $configuration);
70
71
		// Configure Propel
72
		$this->configurePropel($configuration, $logger);
73
74
		// Start the application
75
		$statusManager = new StatusManager($configuration, $logger);
76
		$statusManager->run();
77
	}
78
79
80
	/**
81
	 * Configures and returns the logger instance
82
	 *
83
	 * @param InputInterface  $input
84
	 * @param OutputInterface $output
85
	 * @param Configuration   $configuration
86
	 *
87
	 * @return Logger
88
	 */
89
	private function configureLogger(InputInterface $input, OutputInterface $output, Configuration $configuration)
0 ignored issues
show
Unused Code introduced by
The parameter $input 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...
90
	{
91
		$consoleHandler = new ConsoleHandler($output);
92
		$consoleHandler->setFormatter(new ColoredLineFormatter(null, "[%datetime%] %level_name%: %message%\n"));
93
94
		$logger = new Logger(self::COMMAND_NAME);
95
		$logger->pushHandler($consoleHandler);
96
		$logger->pushProcessor(new PsrLogMessageProcessor());
97
98
		if ($configuration->getLogPath() !== null)
99
		{
100
			$fileHandler = new StreamHandler($configuration->getLogPath());
101
			$logger->pushHandler($fileHandler);
102
		}
103
104
		return $logger;
105
	}
106
107
108
	/**
109
	 * Configurs the database
110
	 *
111
	 * @param Configuration   $configuration
112
	 * @param LoggerInterface $logger
113
	 */
114
	private function configurePropel(Configuration $configuration, LoggerInterface $logger)
115
	{
116
		/* @var StandardServiceContainer $serviceContainer */
117
		$serviceContainer = \Propel\Runtime\Propel::getServiceContainer();
118
		$serviceContainer->checkVersion('2.0.0-dev');
119
		$serviceContainer->setAdapterClass('tvheadend_status_manager', 'sqlite');
120
		$manager = new \Propel\Runtime\Connection\ConnectionManagerSingle();
121
		$manager->setConfiguration([
122
			'classname'  => 'Propel\\Runtime\\Connection\\ConnectionWrapper',
123
			'dsn'        => 'sqlite:' . $configuration->getDatabasePath(),
124
			'user'       => null,
125
			'password'   => '',
126
			'attributes' => [
127
				'ATTR_EMULATE_PREPARES' => false,
128
			],
129
			'settings'   => [
130
				'charset' => 'utf8',
131
				'queries' => [],
132
			],
133
		]);
134
		$manager->setName('tvheadend_status_manager');
135
		$serviceContainer->setConnectionManager('tvheadend_status_manager', $manager);
136
		$serviceContainer->setDefaultDatasource('tvheadend_status_manager');
137
138
		$serviceContainer->setLogger(self::COMMAND_NAME, $logger);
139
	}
140
141
142
	/**
143
	 * Parses the application configuration
144
	 *
145
	 * @param InputInterface $input
146
	 *
147
	 * @return Configuration the parsed configuration
148
	 */
149
	private function parseConfiguration(InputInterface $input)
150
	{
151
		$this->validateArguments($input);
152
153
		$configFile   = $input->getArgument('configFile');
154
		$databaseFile = $input->getArgument('databaseFile');
155
		$logFile      = $input->getArgument('logFile');
156
157
		// Parse the configuration file
158
		$configuration = parse_ini_file($configFile, true);
159
160
		// Check that the file was parsed
161
		if ($configuration === false)
162
			throw new InvalidConfigurationException('Failed to parse the specified configuration file');
163
164
		$instances = [];
165
166
		// Parse sections
167
		foreach ($configuration as $section => $values)
168
		{
169
			switch (self::getSectionType($section))
170
			{
171
				case Configuration::SECTION_TYPE_INSTANCE:
172
					$instances[] = self::parseInstance($section, $values);
173
					break;
174
			}
175
		}
176
177
		// Validate the configuration. We need at least one instance.
178
		if (empty($instances))
179
			throw new InvalidConfigurationException('No instances defined, you need to specify at least one instance');
180
181
		// Create the configuration object
182
		$config = new Configuration($databaseFile, $instances);
183
184
		// Parse options
185
		$updateInterval = floatval($input->getOption(Configuration::OPTION_UPDATE_INTERVAL));
186
		$config->setUpdateInterval($updateInterval);
187
188
		$listenAddress = $input->getOption(Configuration::OPTION_LISTEN_ADDRESS);
189
		$config->setListenAddress($listenAddress);
190
191
		$listenPort = $input->getOption(Configuration::OPTION_LISTEN_PORT);
192
		$config->setListenPort($listenPort);
193
194
		$config->setLogPath($logFile);
195
196
		return $config;
197
	}
198
199
200
	/**
201
	 * @param InputInterface $input
202
	 *
203
	 * @throws InvalidConfigurationException if the arguments are invalid
204
	 */
205
	private function validateArguments(InputInterface $input)
206
	{
207
		$configFile   = $input->getArgument('configFile');
208
		$databasePath = $input->getArgument('databaseFile');
209
		$logFile      = $input->getArgument('logFile');
210
211
		// Check that the configuration file exists
212
		if (!file_exists($configFile))
213
			throw new InvalidConfigurationException('The specified configuration file does not exist');
214
215
		// Check that the database exists and is writable
216
		if (!file_exists($databasePath))
217
			throw new InvalidConfigurationException('The specified database path does not exist');
218
		else if (!is_writable($databasePath))
219
			throw new InvalidConfigurationException('The specified database path is not writable');
220
221
		// Check that the directory of the log file path is writable
222
		if ($logFile !== null && !is_writable(dirname($logFile)))
223
			throw new InvalidConfigurationException('The specified log file path is not writable');
224
	}
225
226
227
	/**
228
	 * @param string $section
229
	 * @param array  $values
230
	 *
231
	 * @return Instance
232
	 */
233
	private static function parseInstance($section, $values)
234
	{
235
		$name    = substr($section, 9);
236
		$address = $values['address'];
237
		$port    = intval($values['port']);
238
239
		$instance = new Instance($name, $address, $port);
240
241
		// Optionally set ignored users
242
		if (isset($values['ignoredUsers']))
243
			$instance->setIgnoredUsers($values['ignoredUsers']);
244
245
		// Optionally set credentials
246
		if (isset($values['username']) && isset($values['password']))
247
			$instance->setCredentials($values['username'], $values['password']);
248
249
		return $instance;
250
	}
251
252
253
	/**
254
	 * Returns the determined section type based on the specified section name
255
	 *
256
	 * @param string $section
257
	 *
258
	 * @return string
259
	 * @throws InvalidConfigurationException if the section type could not be determined
260
	 */
261
	private static function getSectionType($section)
262
	{
263
		if (substr($section, 0, 8) === 'instance')
264
			return Configuration::SECTION_TYPE_INSTANCE;
265
266
		throw new InvalidConfigurationException('Unknown section "' . $section . '"');
267
	}
268
269
}
270