Completed
Push — master ( a15a51...774211 )
by Sam
03:39
created

TvheadendStatusManagerCommand::configurePropel()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 26
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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