Resources::findFreeResource()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 5
nc 3
nop 0
1
<?php
2
3
namespace Nopolabs\Yabot\Plugins\Reservations;
4
5
use DateTime;
6
use GuzzleHttp\Promise\FulfilledPromise;
7
use GuzzleHttp\Promise\PromiseInterface;
8
use function GuzzleHttp\Promise\settle;
9
use Nopolabs\Yabot\Helpers\LogTrait;
10
use Nopolabs\Yabot\Slack\Client;
11
use Nopolabs\Yabot\Helpers\ConfigTrait;
12
use Nopolabs\Yabot\Helpers\LoopTrait;
13
use Nopolabs\Yabot\Helpers\SlackTrait;
14
use Nopolabs\Yabot\Helpers\StorageTrait;
15
use Nopolabs\Yabot\Storage\StorageInterface;
16
use Psr\Log\LoggerInterface;
17
use React\EventLoop\LoopInterface;
18
use Slack\User;
19
20
/**
21
 * $config = [
22
 *     'channel' => 'general',
23
 *     'storageName' => 'resources',
24
 *     'keys' => ['dev1','dev2'],
25
 *     'defaultReservation' => '+12 hours',
26
 * ];
27
 */
28
class Resources implements ResourcesInterface
29
{
30
    use StorageTrait;
31
    use LoopTrait;
32
    use SlackTrait;
33
    use ConfigTrait;
34
    use LogTrait;
35
36
    protected $channel;
37
    protected $resources;
38
39
    public function __construct(
40
        Client $slack,
41
        StorageInterface $storage,
42
        LoopInterface $eventLoop,
43
        LoggerInterface $logger,
44
        array $config)
45
    {
46
        $this->setConfig($config);
47
        $this->setLog($logger);
48
49
        $this->setSlack($slack);
50
51
        $this->setStorage($storage);
52
        $this->setStorageKey($this->get('storageName', 'resources'));
53
54
        $this->setLoop($eventLoop);
55
        $this->addPeriodicTimer(60, [$this, 'expireResources']);
56
57
        $this->channel = $this->get('channel');
58
59
        $resources = $this->load() ?: [];
60
        $this->resources = [];
61
62
        /** @var array $keys */
63
        $keys = $this->get('keys', []);
64
        foreach ($keys as $key) {
65
            $resource = $resources[$key] ?? [];
66
            $this->resources[$key] = $resource;
67
        }
68
69
        $this->save($this->resources);
70
    }
71
72
    public function isResource($key)
73
    {
74
        return array_key_exists($key, $this->resources);
75
    }
76
77
    public function getResource($key)
78
    {
79
        return $this->isResource($key) ? $this->resources[$key] : null;
80
    }
81
82
    public function getAll() : array
83
    {
84
        return $this->resources;
85
    }
86
87
    public function getKeys() : array
88
    {
89
        return array_keys($this->resources);
90
    }
91
92
    public function isReserved($key) : bool
93
    {
94
        return !empty($this->resources[$key]);
95
    }
96
97
    public function isReservedBy($key, User $user) : bool
98
    {
99
        return isset($this->resources[$key]['user']) && $this->resources[$key]['user'] === $user->getUsername();
100
    }
101
102
    public function findFreeResource()
103
    {
104
        foreach ($this->getKeys() as $key) {
105
            if (!$this->isReserved($key)) {
106
                return $key;
107
            }
108
        }
109
110
        return null;
111
    }
112
113
    public function findUserResource(User $user)
114
    {
115
        foreach ($this->getKeys() as $key) {
116
            if ($this->isReservedBy($key, $user)) {
117
                return $key;
118
            }
119
        }
120
121
        return null;
122
    }
123
124
    public function reserve($key, User $user, DateTime $until = null)
125
    {
126
        $until = $until ?? $this->getDefaultReservationTime();
127
128
        $this->setResource($key, [
129
            'user' => $user->getUsername(),
130
            'userId' => $user->getId(),
131
            'until' => $until->format('Y-m-d H:i:s'),
132
        ]);
133
    }
134
135
    public function release($key)
136
    {
137
        $this->setResource($key, []);
138
    }
139
140
    public function getStatus($key)
141
    {
142
        return $this->getStatusAsync($key)->wait();
143
    }
144
145
    public function getAllStatuses() : array
146
    {
147
        return $this->getStatuses($this->getKeys());
148
    }
149
150
    public function getUserStatuses(User $user) : array
151
    {
152
        $userKeys = array_filter($this->getKeys(), function($key) use ($user) {
153
            return $this->isReservedBy($key, $user);
154
        });
155
156
        return $this->getStatuses($userKeys);
157
    }
158
159
    public function getStatuses(array $keys) : array
160
    {
161
        $requests = [];
162
163
        foreach ($keys as $key) {
164
            $requests[] = $this->getStatusAsync($key);
165
        }
166
167
        $statuses = [];
168
        /** @var array $results */
169
        $results = settle($requests)->wait();
170
        foreach ($results as $key => $result) {
171
            if ($result['state'] === PromiseInterface::FULFILLED) {
172
                $statuses[] = $result['value'];
173
            }
174
        }
175
176
        return $statuses;
177
    }
178
179
    public function forever() : DateTime
180
    {
181
        return new DateTime('3000-01-01');
182
    }
183
184
    protected function setResource($key, $resource)
185
    {
186
        $this->resources[$key] = $resource;
187
        $this->save($this->resources);
188
    }
189
190
    protected function getStatusAsync($key) : PromiseInterface
191
    {
192
        $status = json_encode([$key => $this->resources[$key]]);
193
194
        return new FulfilledPromise($status);
195
    }
196
197
    protected function expireResources()
198
    {
199
        foreach ($this->getKeys() as $key) {
200
            if ($this->isExpired($key)) {
201
                $this->release($key);
202
                $this->say("released $key", $this->channel);
203
            }
204
        }
205
    }
206
207
    protected function isExpired($key) : bool
208
    {
209
        if ($this->isReserved($key)) {
210
            $until = $this->getResource($key)['until'];
211
            $expires = new DateTime($until);
212
213
            return $expires < new DateTime();
214
        }
215
216
        return false;
217
    }
218
219
    protected function getDefaultReservationTime() : DateTime
220
    {
221
        return new DateTime($this->get('defaultReservation', '+12 hours'));
222
    }
223
}