Completed
Push — master ( 774211...4ab75d )
by Sam
02:06
created

TvheadendStatusManagerCommand::validateArguments()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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