Completed
Push — master ( 9f7a88...4710e7 )
by Sam
06:06
created

StatusManager::handleInstanceStatus()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 33
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 33
ccs 0
cts 16
cp 0
rs 8.439
cc 5
eloc 15
nc 16
nop 1
crap 30
1
<?php
2
3
namespace Jalle19\StatusManager\Manager;
4
5
use Jalle19\StatusManager\Configuration\Instance;
6
use Jalle19\StatusManager\Event\ConnectionSeenEvent;
7
use Jalle19\StatusManager\Event\Events;
8
use Jalle19\StatusManager\Event\InputSeenEvent;
9
use Jalle19\StatusManager\Event\InstanceStatusCollectionRequestEvent;
10
use Jalle19\StatusManager\Event\InstanceSeenEvent;
11
use Jalle19\StatusManager\Event\InstanceStateEvent;
12
use Jalle19\StatusManager\Event\InstanceStatusUpdatesEvent;
13
use Jalle19\StatusManager\Event\SubscriptionSeenEvent;
14
use Jalle19\StatusManager\Event\SubscriptionStateChangeEvent;
15
use Jalle19\StatusManager\Instance\InstanceStatus;
16
use Jalle19\StatusManager\Instance\InstanceStatusCollection;
17
use Jalle19\StatusManager\Subscription\StateChangeParser;
18
use Jalle19\tvheadend\exception\RequestFailedException;
19
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
20
21
/**
22
 * Class StatusManager
23
 * @package   Jalle19\StatusManager\Manager
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 StatusManager extends AbstractManager implements EventSubscriberInterface
28
{
29
30
	/**
31
	 * @inheritdoc
32
	 */
33 1
	public static function getSubscribedEvents()
34
	{
35
		return [
36 1
			Events::MAIN_LOOP_STARTING => 'onMainLoopStarted',
37
			Events::MAIN_LOOP_TICK     => 'onMainLoopTick',
38
		];
39
	}
40
41
42
	/**
43
	 * Called right before the main loop is started
44
	 */
45
	public function onMainLoopStarted()
46
	{
47
		// Log information about the database
48
		$this->logger->notice('Using database at {databasePath}', [
49
			'databasePath' => $this->configuration->getDatabasePath(),
50
		]);
51
52
		// Log information about the configured instances
53
		$instances = $this->configuration->getInstances();
54
55
		$this->logger->notice('Managing {instances} instances:', [
56
			'instances' => count($instances),
57
		]);
58
59
		foreach ($instances as $configuredInstance)
60
		{
61
			$instance = $configuredInstance->getInstance();
62
63
			$this->logger->notice('  {address}:{port}', [
64
				'address' => $instance->getHostname(),
65
				'port'    => $instance->getPort(),
66
			]);
67
68
			$this->eventDispatcher->dispatch(Events::INSTANCE_SEEN, new InstanceSeenEvent($instance));
69
		}
70
	}
71
72
73
	/**
74
	 * Called periodically by the event loop
75
	 */
76
	public function onMainLoopTick()
77
	{
78
		/* @var InstanceStatusCollectionRequestEvent $event */
79
		$event = $this->eventDispatcher->dispatch(Events::INSTANCE_STATUS_COLLECTION_REQUEST,
80
			new InstanceStatusCollectionRequestEvent());
81
82
		$statusCollection = $this->getStatusMessages($event->getInstanceStatusCollection());
83
84
		foreach ($statusCollection->getInstanceStatuses() as $instanceStatus)
85
			$this->handleInstanceStatus($instanceStatus);
86
87
		$this->eventDispatcher->dispatch(Events::INSTANCE_STATUS_UPDATES,
88
			new InstanceStatusUpdatesEvent($statusCollection));
89
	}
90
91
92
	/**
93
	 * @param InstanceStatus $instanceStatus
94
	 */
95
	private function handleInstanceStatus(InstanceStatus $instanceStatus)
96
	{
97
		$instanceName = $instanceStatus->getInstanceName();
98
99
		$this->logger->debug('Got status updates from {instanceName}', [
100
			'instanceName' => $instanceName,
101
		]);
102
103
		// Persist connections
104
		foreach ($instanceStatus->getConnections() as $connection)
105
		{
106
			$this->eventDispatcher->dispatch(Events::CONNECTION_SEEN,
107
				new ConnectionSeenEvent($instanceName, $connection));
108
		}
109
110
		// Persist inputs
111
		foreach ($instanceStatus->getInputs() as $input)
112
			$this->eventDispatcher->dispatch(Events::INPUT_SEEN, new InputSeenEvent($instanceName, $input));
113
114
		// Persist running subscriptions
115
		foreach ($instanceStatus->getSubscriptions() as $subscription)
116
		{
117
			$this->eventDispatcher->dispatch(Events::SUBSCRIPTION_SEEN,
118
				new SubscriptionSeenEvent($instanceName, $subscription));
119
		}
120
121
		// Handle subscription state changes
122
		foreach ($instanceStatus->getSubscriptionStateChanges() as $subscriptionStateChange)
123
		{
124
			$this->eventDispatcher->dispatch(Events::SUBSCRIPTION_STATE_CHANGE,
125
				new SubscriptionStateChangeEvent($instanceName, $subscriptionStateChange));
126
		}
127
	}
128
129
130
	/**
131
	 * Retrieves and returns all status messages for the configured
132
	 * instances
133
	 *
134
	 * @param \SplObjectStorage $instances the instances and their state
135
	 *
136
	 * @return InstanceStatusCollection
137
	 */
138
	private function getStatusMessages($instances)
139
	{
140
		$collection = new InstanceStatusCollection();
141
142
		foreach ($instances as $instance)
143
		{
144
			/* @var Instance $instance */
145
			$tvheadend     = $instance->getInstance();
146
			$instanceName  = $instance->getName();
147
			$instanceState = $instances[$instance];
148
149
			// Collect statuses from currently reachable instances
150
			if ($instanceState->isReachable())
151
			{
152
				try
153
				{
154
					$collection->add(new InstanceStatus(
155
						$instanceName,
156
						$tvheadend->getInputStatus(),
157
						$tvheadend->getSubscriptionStatus(),
158
						$tvheadend->getConnectionStatus(),
159
						StateChangeParser::parseStateChanges($tvheadend->getLogMessages())));
160
161
					$this->eventDispatcher->dispatch(Events::INSTANCE_STATE_REACHABLE,
162
						new InstanceStateEvent($instance));
163
				}
164
				catch (\Exception $e)
165
				{
166
					if ($e instanceof RequestFailedException)
167
					{
168
						// Check for authentication errors
169
						$statusCode = $e->getResponse()->getStatusCode();
170
171
						if ($statusCode >= 400 && $statusCode < 500)
172
						{
173
							$this->logger->error('Authentication/authorization failed when connecting to {instanceName} (HTTP {statusCode})',
174
								[
175
									'instanceName' => $instanceName,
176
									'statusCode'   => $e->getResponse()->getStatusCode(),
177
								]);
178
						}
179
					}
180
181
					$this->eventDispatcher->dispatch(Events::INSTANCE_STATE_UNREACHABLE,
182
						new InstanceStateEvent($instance));
183
				}
184
			}
185
			else
186
			{
187
				$this->eventDispatcher->dispatch(Events::INSTANCE_STATE_MAYBE_REACHABLE,
188
					new InstanceStateEvent($instance));
189
			}
190
		}
191
192
		return $collection;
193
	}
194
195
}
196