Completed
Push — master ( b2dfe1...1412d2 )
by Sam
02:32
created

PersistenceManager::hasUser()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 2
1
<?php
2
3
namespace Jalle19\StatusManager\Manager;
4
5
use Jalle19\StatusManager\Database;
6
use Jalle19\StatusManager\Database\Channel;
7
use Jalle19\StatusManager\Database\ChannelQuery;
8
use Jalle19\StatusManager\Database\Connection;
9
use Jalle19\StatusManager\Database\ConnectionQuery;
10
use Jalle19\StatusManager\Database\Input;
11
use Jalle19\StatusManager\Database\InputQuery;
12
use Jalle19\StatusManager\Database\InstanceQuery;
13
use Jalle19\StatusManager\Database\Subscription;
14
use Jalle19\StatusManager\Database\SubscriptionQuery;
15
use Jalle19\StatusManager\Database\User;
16
use Jalle19\StatusManager\Database\UserQuery;
17
use Jalle19\StatusManager\Event\ConnectionSeenEvent;
18
use Jalle19\StatusManager\Event\InputSeenEvent;
19
use Jalle19\StatusManager\Event\InstanceSeenEvent;
20
use Jalle19\StatusManager\Event\SubscriptionSeenEvent;
21
use Jalle19\StatusManager\Event\SubscriptionStateChangeEvent;
22
use Jalle19\StatusManager\Subscription\StateChange;
23
use Jalle19\tvheadend\model\ConnectionStatus;
24
use Jalle19\tvheadend\model\SubscriptionStatus;
25
use Jalle19\tvheadend\Tvheadend;
26
27
/**
28
 * Handles persisting of objects to the database
29
 *
30
 * @package   Jalle19\StatusManager\Manager
31
 * @copyright Copyright &copy; Sam Stenvall 2015-
32
 * @license   https://www.gnu.org/licenses/gpl.html The GNU General Public License v2.0
33
 */
34
class PersistenceManager extends AbstractManager
35
{
36
37
	/**
38
	 * Removes stale subscriptions that haven't received a stop event
39
	 */
40
	public function onMainLoopStarted()
41
	{
42
		$numRemoved = SubscriptionQuery::create()->filterByStopped(null)->delete();
43
44
		$this->getApplication()->getLogger()->info('Removed {numRemoved} stale subscriptions', [
45
			'numRemoved' => $numRemoved,
46
		]);
47
	}
48
49
50
	/**
51
	 * @param InstanceSeenEvent $event
52
	 *
53
	 * @throws \Propel\Runtime\Exception\PropelException
54
	 */
55
	public function onInstanceSeen(InstanceSeenEvent $event)
56
	{
57
		$instance = $event->getInstance();
58
59
		if ($this->hasInstance($instance))
60
			return;
61
62
		$instanceModel = new Database\Instance();
63
		$instanceModel->setPrimaryKey($instance->getHostname());
64
		$instanceModel->save();
65
66
		$this->getApplication()->getLogger()->info('Stored new instance {instanceName}', [
67
			'instanceName' => $instance->getHostname(),
68
		]);
69
70
		// Create a special user for eventual DVR subscriptions
71
		$user = new User();
72
		$user->setInstance($instanceModel);
73
		$user->setName(User::NAME_DVR);
74
		$user->save();
75
76
		$this->getApplication()->getLogger()
77
		     ->info('Stored new special user (instance: {instanceName}, user: {userName})', [
78
			     'instanceName' => $instance->getHostname(),
79
			     'userName'     => $user->getName(),
80
		     ]);
81
	}
82
83
84
	/**
85
	 * @param ConnectionSeenEvent $event
86
	 */
87
	public function onConnectionSeen(ConnectionSeenEvent $event)
88
	{
89
		$instanceName     = $event->getInstance();
90
		$connectionStatus = $event->getConnection();
91
92
		if ($this->hasConnection($instanceName, $connectionStatus))
93
			return;
94
95
		$user = null;
96
97
		// Find the user object when applicable
98
		if (!$connectionStatus->isAnonymous())
99
		{
100
			$this->onUserSeen($instanceName, $connectionStatus->user);
101
102
			$user = UserQuery::create()->filterByInstanceName($instanceName)->filterByName($connectionStatus->user)
103
			                 ->findOne();
104
		}
105
106
		$connection = new Connection();
107
		$connection->setInstanceName($instanceName)->setPeer($connectionStatus->peer)
108
		           ->setUser($user)
109
		           ->setStarted($connectionStatus->started)->setType($connectionStatus->type)->save();
110
111
		$this->getApplication()->getLogger()->info('Stored new connection (instance: {instanceName}, peer: {peer})', [
112
			'instanceName' => $instanceName,
113
			'peer'         => $connectionStatus->peer,
114
		]);
115
	}
116
117
118
	/**
119
	 * @param InputSeenEvent $event
120
	 */
121
	public function onInputSeen(InputSeenEvent $event)
122
	{
123
		$instanceName = $event->getInstance();
124
		$inputStatus  = $event->getInput();
125
126
		// Update the input and started fields for existing inputs
127
		if ($this->hasInput($inputStatus->uuid))
128
		{
129
			$input = InputQuery::create()->findPk($inputStatus->uuid);
130
			$input->setStarted(new \DateTime())->setWeight($inputStatus->weight);
131
132
			return;
133
		}
134
135
		$input = new Input();
136
		$input->setPrimaryKey($inputStatus->uuid);
137
		$input->setInstanceName($instanceName)
138
		      ->setStarted(new \DateTime())
139
		      ->setInput($inputStatus->input)
140
		      ->setWeight($inputStatus->weight)
141
		      ->setNetwork(Input::parseNetwork($inputStatus))
142
		      ->setMux(Input::parseMux($inputStatus))->save();
143
144
		$this->getApplication()->getLogger()
145
		     ->info('Stored new input (instance: {instanceName}, network: {network}, mux: {mux}, weight: {weight})',
146
			     [
147
				     'instanceName' => $instanceName,
148
				     'network'      => $input->getNetwork(),
149
				     'mux'          => $input->getMux(),
150
				     'weight'       => $input->getWeight(),
151
			     ]);
152
	}
153
154
155
	/**
156
	 * @param SubscriptionSeenEvent $event
157
	 *
158
	 * @throws \Propel\Runtime\Exception\PropelException
159
	 */
160
	public function onSubscriptionSeen(SubscriptionSeenEvent $event)
161
	{
162
		$instanceName = $event->getInstance();
163
		$status       = $event->getSubscription();
164
165
		// Ignore certain subscriptions
166
		if (in_array($status->getType(), [SubscriptionStatus::TYPE_EPGGRAB, SubscriptionStatus::TYPE_SERVICE_OR_MUX]))
167
			return;
168
169
		// Determine the username to store for the subscription
170
		$username = $status->username;
171
172
		switch ($status->getType())
173
		{
174
			case SubscriptionStatus::TYPE_RECORDING:
175
				$username = 'dvr';
176
				break;
177
		}
178
179
		// Get the instance, user and channel
180
		$instance = InstanceQuery::create()->findPk($instanceName);
181
		$user     = UserQuery::create()->filterByInstance($instance)->filterByName($username)->findOne();
182
183
		// Ensure the channel exists
184
		$this->onChannelSeen($instanceName, $status->channel);
185
		$channel = ChannelQuery::create()->filterByInstance($instance)->filterByName($status->channel)->findOne();
186
187
		if ($this->hasSubscription($instance, $user, $channel, $status))
188
			return;
189
190
		// Try to determine which input is used by the subscription
191
		$input = InputQuery::create()->filterBySubscriptionStatus($instanceName, $status)->findOne();
192
193
		if ($input === null)
194
		{
195
			$this->getApplication()->getLogger()
196
			     ->warning('Got subscription that cannot be tied to an input ({instanceName}, user: {userName}, channel: {channelName})',
197
				     [
198
					     'instanceName' => $instanceName,
199
					     'userName'     => $user !== null ? $user->getName() : 'N/A',
200
					     'channelName'  => $channel->getName(),
201
				     ]);
202
		}
203
204
		$subscription = new Subscription();
205
		$subscription->setInstance($instance)->setInput($input)->setUser($user)->setChannel($channel)
206
		             ->setSubscriptionId($status->id)->setStarted($status->start)->setTitle($status->title)
207
		             ->setService($status->service);
208
		$subscription->save();
209
210
		$this->getApplication()->getLogger()
211
		     ->info('Stored new subscription (instance: {instanceName}, user: {userName}, channel: {channelName})',
212
			     [
213
				     'instanceName' => $instanceName,
214
				     'userName'     => $user !== null ? $user->getName() : 'N/A',
215
				     'channelName'  => $channel->getName(),
216
			     ]);
217
	}
218
219
220
	/**
221
	 * @param SubscriptionStateChangeEvent $event
222
	 */
223
	public function onSubscriptionStateChange(SubscriptionStateChangeEvent $event)
224
	{
225
		$instanceName = $event->getInstance();
226
		$stateChange  = $event->getStateChange();
227
228
		// We only need to persist subscription stops
229
		if ($stateChange->getState() === StateChange::STATE_SUBSCRIPTION_STARTED)
230
			return;
231
232
		// Find the latest matching subscription
233
		$subscription = SubscriptionQuery::create()->filterByInstanceName($instanceName)
234
		                                 ->filterBySubscriptionId($stateChange->getSubscriptionId())
235
		                                 ->addDescendingOrderByColumn('started')->findOne();
236
237
		// EPG grab subscriptions are not stored so we don't want to log these with a high level
238
		if ($subscription === null)
239
		{
240
			$this->getApplication()->getLogger()
241
			     ->debug('Got subscription stop without a matching start (instance: {instanceName}, subscription: {subscriptionId})',
242
				     [
243
					     'instanceName'   => $instanceName,
244
					     'subscriptionId' => $stateChange->getSubscriptionId(),
245
				     ]);
246
247
			return;
248
		}
249
250
		$subscription->setStopped(new \DateTime());
251
		$subscription->save();
252
253
		$user    = $subscription->getUser();
254
		$channel = $subscription->getChannel();
255
256
		$this->getApplication()->getLogger()
257
		     ->info('Stored subscription stop (instance: {instanceName}, user: {userName}, channel: {channelName})',
258
			     [
259
				     'instanceName' => $instanceName,
260
				     'userName'     => $user !== null ? $user->getName() : 'N/A',
261
				     'channelName'  => $channel->getName(),
262
			     ]);
263
	}
264
265
266
	/**
267
	 * @param string $instanceName
268
	 * @param string $userName
269
	 *
270
	 * @throws \Propel\Runtime\Exception\PropelException
271
	 */
272 View Code Duplication
	private function onUserSeen($instanceName, $userName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
273
	{
274
		if ($this->hasUser($instanceName, $userName))
275
			return;
276
277
		$user = new User();
278
		$user->setInstanceName($instanceName)->setName($userName);
279
		$user->save();
280
281
		$this->getApplication()->getLogger()->info('Stored new user (instance: {instanceName}, username: {userName})', [
282
			'instanceName' => $instanceName,
283
			'userName'     => $userName,
284
		]);
285
	}
286
287
288
	/**
289
	 * @param string $instanceName
290
	 * @param string $channelName
291
	 *
292
	 * @throws \Propel\Runtime\Exception\PropelException
293
	 */
294 View Code Duplication
	private function onChannelSeen($instanceName, $channelName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
295
	{
296
		if ($this->hasChannel($instanceName, $channelName))
297
			return;
298
299
		$channel = new Channel();
300
		$channel->setInstanceName($instanceName)->setName($channelName);
301
		$channel->save();
302
303
		$this->getApplication()->getLogger()
304
		     ->info('Stored new channel (instance: {instanceName}, name: {channelName})', [
305
			     'instanceName' => $instanceName,
306
			     'channelName'  => $channelName,
307
		     ]);
308
	}
309
310
311
	/**
312
	 * @param Tvheadend $instance
313
	 *
314
	 * @return bool whether the instance exists in the database
315
	 */
316
	private function hasInstance(Tvheadend $instance)
317
	{
318
		return InstanceQuery::create()->findPk($instance->getHostname()) !== null;
319
	}
320
321
322
	/**
323
	 * @param                  $instanceName
324
	 * @param ConnectionStatus $connectionStatus
325
	 *
326
	 * @return bool whether the connection exists in the database
327
	 */
328
	private function hasConnection($instanceName, ConnectionStatus $connectionStatus)
329
	{
330
		return ConnectionQuery::create()->filterByInstanceName($instanceName)->filterByPeer($connectionStatus->peer)
331
		                      ->filterByStarted($connectionStatus->started)->findOne() !== null;
332
	}
333
334
335
	/**
336
	 * @param string $uuid
337
	 *
338
	 * @return bool
339
	 */
340
	private function hasInput($uuid)
341
	{
342
		return InputQuery::create()->findPk($uuid) !== null;
343
	}
344
345
346
	/**
347
	 * @param string $instanceName
348
	 * @param string $userName
349
	 *
350
	 * @return bool
351
	 */
352
	private function hasUser($instanceName, $userName)
353
	{
354
		return UserQuery::create()->filterByInstanceName($instanceName)->filterByName($userName)->findOne() !== null;
355
	}
356
357
358
	/**
359
	 * @param string $instanceName
360
	 * @param string $channelName
361
	 *
362
	 * @return bool
363
	 */
364
	private function hasChannel($instanceName, $channelName)
365
	{
366
		return ChannelQuery::create()->filterByInstanceName($instanceName)->filterByName($channelName)
367
		                   ->findOne() !== null;
368
	}
369
370
371
	/**
372
	 * @param Database\Instance  $instance
373
	 * @param User|null          $user
374
	 * @param Channel            $channel
375
	 * @param SubscriptionStatus $subscription
376
	 *
377
	 * @return bool
378
	 * @throws \Propel\Runtime\Exception\PropelException
379
	 */
380
	private function hasSubscription(
381
		Database\Instance $instance,
382
		$user,
383
		Channel $channel,
384
		SubscriptionStatus $subscription
385
	) {
386
		// Not all subscriptions are tied to a user
387
		$userId = $user !== null ? $user->getId() : null;
388
389
		return SubscriptionQuery::create()->filterByInstance($instance)->filterByUserId($userId)
390
		                        ->filterByChannel($channel)
391
		                        ->filterBySubscriptionId($subscription->id)->filterByStarted($subscription->start)
392
		                        ->findOne() !== null;
393
	}
394
395
}
396