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

TvheadendStatusManagerCommand::execute()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 1 Features 2
Metric Value
c 8
b 1
f 2
dl 0
loc 26
rs 8.8571
cc 2
eloc 13
nc 2
nop 2
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
		$consoleHandler = new ConsoleHandler($output);
70
		$consoleHandler->setFormatter(new ColoredLineFormatter(null, "[%datetime%] %level_name%: %message%\n"));
71
72
		$logger = new Logger(self::COMMAND_NAME);
73
		$logger->pushHandler($consoleHandler);
74
		$logger->pushProcessor(new PsrLogMessageProcessor());
75
76
		if ($configuration->getLogPath() !== null)
77
		{
78
			$fileHandler = new StreamHandler($configuration->getLogPath());
79
			$logger->pushHandler($fileHandler);
80
		}
81
82
		// Configure Propel
83
		$this->configurePropel($configuration, $logger);
84
85
		// Start the application
86
		$statusManager = new StatusManager($configuration, $logger);
87
		$statusManager->run();
88
	}
89
90
91
	/**
92
	 * Configurs the database
93
	 *
94
	 * @param Configuration   $configuration
95
	 * @param LoggerInterface $logger
96
	 */
97
	private function configurePropel(Configuration $configuration, LoggerInterface $logger)
98
	{
99
		/* @var StandardServiceContainer $serviceContainer */
100
		$serviceContainer = \Propel\Runtime\Propel::getServiceContainer();
101
		$serviceContainer->checkVersion('2.0.0-dev');
102
		$serviceContainer->setAdapterClass('tvheadend_status_manager', 'sqlite');
103
		$manager = new \Propel\Runtime\Connection\ConnectionManagerSingle();
104
		$manager->setConfiguration([
105
			'classname'  => 'Propel\\Runtime\\Connection\\ConnectionWrapper',
106
			'dsn'        => 'sqlite:' . $configuration->getDatabasePath(),
107
			'user'       => null,
108
			'password'   => '',
109
			'attributes' => [
110
				'ATTR_EMULATE_PREPARES' => false,
111
			],
112
			'settings'   => [
113
				'charset' => 'utf8',
114
				'queries' => [],
115
			],
116
		]);
117
		$manager->setName('tvheadend_status_manager');
118
		$serviceContainer->setConnectionManager('tvheadend_status_manager', $manager);
119
		$serviceContainer->setDefaultDatasource('tvheadend_status_manager');
120
121
		$serviceContainer->setLogger(self::COMMAND_NAME, $logger);
122
	}
123
124
125
	/**
126
	 * Parses the application configuration
127
	 *
128
	 * @param InputInterface $input
129
	 *
130
	 * @return Configuration the parsed configuration
131
	 */
132
	private function parseConfiguration(InputInterface $input)
133
	{
134
		$this->validateArguments($input);
135
136
		$configFile   = $input->getArgument('configFile');
137
		$databaseFile = $input->getArgument('databaseFile');
138
		$logFile      = $input->getArgument('logFile');
139
140
		// Parse the configuration file
141
		$configuration = parse_ini_file($configFile, true);
142
143
		// Check that the file was parsed
144
		if ($configuration === false)
145
			throw new InvalidConfigurationException('Failed to parse the specified configuration file');
146
147
		$instances = [];
148
149
		// Parse sections
150
		foreach ($configuration as $section => $values)
151
		{
152
			switch (self::getSectionType($section))
153
			{
154
				case Configuration::SECTION_TYPE_INSTANCE:
155
					$instances[] = $this->parseInstance($section, $values);
156
					break;
157
			}
158
		}
159
160
		// Validate the configuration. We need at least one instance.
161
		if (empty($instances))
162
			throw new InvalidConfigurationException('No instances defined, you need to specify at least one instance');
163
164
		// Create the configuration object
165
		$config = new Configuration($databaseFile, $instances);
166
167
		// Parse options
168
		$updateInterval = floatval($input->getOption(Configuration::OPTION_UPDATE_INTERVAL));
169
		$config->setUpdateInterval($updateInterval);
170
171
		$listenAddress = $input->getOption(Configuration::OPTION_LISTEN_ADDRESS);
172
		$config->setListenAddress($listenAddress);
173
174
		$listenPort = $input->getOption(Configuration::OPTION_LISTEN_PORT);
175
		$config->setListenPort($listenPort);
176
177
		$config->setLogPath($logFile);
178
179
		return $config;
180
	}
181
182
183
	/**
184
	 * @param InputInterface $input
185
	 *
186
	 * @throws InvalidConfigurationException if the arguments are invalid
187
	 */
188
	private function validateArguments(InputInterface $input)
189
	{
190
		$configFile   = $input->getArgument('configFile');
191
		$databasePath = $input->getArgument('databaseFile');
192
		$logFile      = $input->getArgument('logFile');
193
194
		// Check that the configuration file exists
195
		if (!file_exists($configFile))
196
			throw new InvalidConfigurationException('The specified configuration file does not exist');
197
198
		// Check that the database exists and is writable
199
		if (!file_exists($databasePath))
200
			throw new InvalidConfigurationException('The specified database path does not exist');
201
		else if (!is_writable($databasePath))
202
			throw new InvalidConfigurationException('The specified database path is not writable');
203
204
		// Check that the directory of the log file path is writable
205
		if ($logFile !== null && !is_writable(dirname($logFile)))
206
			throw new InvalidConfigurationException('The specified log file path is not writable');
207
	}
208
209
210
	/**
211
	 * @param string $section
212
	 * @param array  $values
213
	 *
214
	 * @return Instance
215
	 */
216
	private function parseInstance($section, $values)
217
	{
218
		$name    = substr($section, 9);
219
		$address = $values['address'];
220
		$port    = intval($values['port']);
221
222
		$instance = new Instance($name, $address, $port);
223
224
		// Optionally set ignored users
225
		if (isset($values['ignoredUsers']))
226
			$instance->setIgnoredUsers($values['ignoredUsers']);
227
228
		// Optionally set credentials
229
		if (isset($values['username']) && isset($values['password']))
230
			$instance->setCredentials($values['username'], $values['password']);
231
232
		return $instance;
233
	}
234
235
236
	/**
237
	 * Returns the determined section type based on the specified section name
238
	 *
239
	 * @param string $section
240
	 *
241
	 * @return string
242
	 * @throws InvalidConfigurationException if the section type could not be determined
243
	 */
244
	private static function getSectionType($section)
245
	{
246
		if (substr($section, 0, 8) === 'instance')
247
			return Configuration::SECTION_TYPE_INSTANCE;
248
249
		throw new InvalidConfigurationException('Unknown section "' . $section . '"');
250
	}
251
252
}
253