Lines 90.00% 18 / 20
Functions and Methods 75.00% 3 / 4
Classes and Traits 0.00% 0 / 1
Name Lines Functions and Methods CRAP Classes and Traits
RegistryRefresher 90.00% 18 / 20 75.00% 3 / 4 8.06 0.00% 0 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 scheduleRefreshFor 100.00% 4 / 4 100.00% 1 / 1 2
 startRefresh 84.61% 11 / 13 0.00% 0 / 1 3.03
 refreshAndBroadcast 100.00% 2 / 2 100.00% 1 / 1 2
1<?php
2
3declare(strict_types=1);
4
5namespace Stewart\Runtime\Broker;
6
7use Psr\Log\LoggerInterface;
8use Stewart\Contracts\Event\HaEvent;
9use Stewart\Contracts\Time\Duration;
10use Stewart\Contracts\Time\TimerHandle;
11use Stewart\Contracts\Time\Timers;
12use Throwable;
13
14use function Amp\async;
15
16final class RegistryRefresher
17{
18    private const array REFRESHING_EVENT_TYPES = [
19        'area_registry_updated',
20        'floor_registry_updated',
21        'label_registry_updated',
22        'device_registry_updated',
23        'entity_registry_updated',
24    ];
25
26    private const int QUIET_PERIOD_MILLISECONDS = 1000;
27
28    private ?TimerHandle $pendingRefresh = null;
29
30    private bool $refreshing = false;
31
32    private bool $refreshRequestedWhileRefreshing = false;
33
34    public function __construct(
35        private readonly HaSession $session,
36        private readonly WorkerSlotRegistry $slots,
37        private readonly Timers $timers,
38        private readonly LoggerInterface $logger,
39    ) {}
40
41    public function scheduleRefreshFor(HaEvent $event): void
42    {
43        if (!\in_array($event->type, self::REFRESHING_EVENT_TYPES, true)) {
44            return;
45        }
46
47        $this->pendingRefresh?->cancel();
48        $this->pendingRefresh = $this->timers->startTimer(Duration::milliseconds(self::QUIET_PERIOD_MILLISECONDS), $this->startRefresh(...));
49    }
50
51    private function startRefresh(): void
52    {
53        $this->pendingRefresh = null;
54
55        if ($this->refreshing) {
56            $this->refreshRequestedWhileRefreshing = true;
57
58            return;
59        }
60
61        $this->refreshing = true;
62
63        async(function (): void {
64            try {
65                do {
66                    $this->refreshRequestedWhileRefreshing = false;
67                    $this->refreshAndBroadcast();
68                } while ($this->refreshRequestedWhileRefreshing);
69            } catch (Throwable $e) {
70                $this->logger->error('Failed refreshing the Home Assistant registry', ['exception' => $e]);
71            } finally {
72                $this->refreshing = false;
73            }
74        })->ignore();
75    }
76
77    private function refreshAndBroadcast(): void
78    {
79        if ($this->session->refreshRegistry()) {
80            $this->slots->broadcast($this->session->snapshotRegistry()->toRegistrySnapshot());
81        }
82    }
83}