/ src / Controller / CrontabController.php
CrontabController.php
  1  <?php
  2  
  3  namespace App\Controller;
  4  
  5  use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  6  use Symfony\Contracts\Translation\TranslatorInterface;
  7  
  8  use Symfony\Component\Routing\Annotation\Route;
  9  use Symfony\Component\HttpFoundation\Response;
 10  use Symfony\Component\HttpFoundation\Request;
 11  
 12  use App\Entity\Online;
 13  use App\Entity\Player;
 14  use App\Entity\Server;
 15  
 16  use Doctrine\ORM\EntityManagerInterface;
 17  
 18  class CrontabController extends AbstractController
 19  {
 20      #[Route(
 21          '/crontab/index',
 22          name: 'crontab_index',
 23          methods:
 24          [
 25              'GET'
 26          ]
 27      )]
 28      public function index(
 29          ?Request $request,
 30          TranslatorInterface $translatorInterface,
 31          EntityManagerInterface $entityManagerInterface
 32      ): Response
 33      {
 34          // Prevent multi-thread execution
 35          $semaphore = sem_get(
 36              crc32(
 37                  __DIR__ . '.controller.crontab.index',
 38              ), 1
 39          );
 40  
 41          if (false === sem_acquire($semaphore, true))
 42          {
 43              return new Response(
 44                  $translatorInterface->trans('Process locked by another thread')
 45              );
 46          }
 47  
 48          // Get new servers from masters
 49          foreach ((array) explode(',', $this->getParameter('app.masters')) as $master)
 50          {
 51              if (!$host = parse_url($master, PHP_URL_HOST)) // @TODO IPv6 https://bugs.php.net/bug.php?id=72811
 52              {
 53                  continue;
 54              }
 55  
 56              if (!$port = parse_url($master, PHP_URL_PORT))
 57              {
 58                  continue;
 59              }
 60  
 61              // Connect master node
 62              $node = new \Yggverse\Hl\Xash3D\Master($host, $port, 1);
 63  
 64              foreach ((array) $node->getServersIPv6() as $key => $value)
 65              {
 66                  // Generate server identity
 67                  $crc32server = crc32(
 68                      $key
 69                  );
 70  
 71                  // Check server does not exist yet
 72                  $server = $entityManagerInterface->getRepository(Server::class)->findOneBy(
 73                      [
 74                          'crc32server' => $crc32server
 75                      ]
 76                  );
 77  
 78                  // Server exist, just update
 79                  if ($server)
 80                  {
 81                      $server->setUpdated(
 82                          time()
 83                      );
 84  
 85                      $server->setOnline(
 86                          time()
 87                      );
 88  
 89                      $entityManagerInterface->persist(
 90                          $server
 91                      );
 92  
 93                      $entityManagerInterface->flush();
 94  
 95                      continue;
 96                  }
 97  
 98                  // Server does not exist, create new record
 99                  $server = new Server();
100  
101                  $server->setCrc32server(
102                      $crc32server
103                  );
104  
105                  $server->setHost(
106                      $value['host']
107                  );
108  
109                  $server->setPort(
110                      $value['port']
111                  );
112  
113                  $server->setAdded(
114                      time()
115                  );
116  
117                  $server->setUpdated(
118                      time()
119                  );
120  
121                  $server->setOnline(
122                      time()
123                  );
124  
125                  $entityManagerInterface->persist(
126                      $server
127                  );
128  
129                  $entityManagerInterface->flush();
130              }
131          }
132  
133          // Collect servers info
134          $servers = [];
135  
136          foreach ((array) $entityManagerInterface->getRepository(Server::class)->findAll() as $server)
137          {
138              try
139              {
140                  $node = new \xPaw\SourceQuery\SourceQuery();
141  
142                  $node->Connect(
143                      false === filter_var($server->getHost(), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? $server->getHost() : "[{$server->getHost()}]",
144                      $server->getPort(),
145                      1
146                  );
147  
148                  if ($node->Ping())
149                  {
150                      if ($info = (array) $node->GetInfo())
151                      {
152                          // Filter response
153                          $bots    = isset($info['Bots']) && $info['Bots'] > 0 ? (int) $info['Bots'] : 0;
154                          $players = isset($info['Players']) && $info['Players'] > 0 ? (int) $info['Players'] - $bots : 0;
155                          $total   = $players + $bots;
156  
157                          // Update server name
158                          if (!empty($info['HostName']) && mb_strlen($info['HostName']) < 256)
159                          {
160                              $server->setName(
161                                  (string) $info['HostName']
162                              );
163                          }
164  
165                          $server->setUpdated(
166                              time()
167                          );
168  
169                          $server->setOnline(
170                              time()
171                          );
172  
173                          $entityManagerInterface->persist(
174                              $server
175                          );
176  
177                          $entityManagerInterface->flush();
178  
179                          // Get last online value
180                          $online = $entityManagerInterface->getRepository(Online::class)->findOneBy(
181                              [
182                                  'crc32server' => $server->getCrc32server()
183                              ],
184                              [
185                                  'id' => 'DESC' // same as online.time but faster
186                              ]
187                          );
188  
189                          // Add new record if online changed
190                          if
191                          (
192                              is_null($online)
193                              ||
194                              $players !== $online->getPlayers()
195                              // ||
196                              // $bots !== $online->getBots()
197                              // ||
198                              // $total !== $online->getTotal()
199                          )
200                          {
201                              $online = new Online();
202  
203                              $online->setCrc32server(
204                                  $server->getCrc32server()
205                              );
206  
207                              $online->setTime(
208                                  time()
209                              );
210  
211                              $online->setPlayers(
212                                  $players
213                              );
214  
215                              $online->setBots(
216                                  $bots
217                              );
218  
219                              $online->setTotal(
220                                  $total
221                              );
222  
223                              $entityManagerInterface->persist(
224                                  $online
225                              );
226  
227                              $entityManagerInterface->flush();
228                          }
229  
230                          // Update player stats
231                          if ($players)
232                          {
233                              foreach ((array) $node->GetPlayers() as $session)
234                              {
235                                  // Validate fields
236                                  if
237                                  (
238                                      !isset($session['Name']) || mb_strlen($session['Name']) > 255
239                                      ||
240                                      !isset($session['TimeF']) || (int) $session['TimeF'] < 0
241                                      ||
242                                      !isset($session['Frags']) || (int) $session['Frags'] < 0
243                                  )
244                                  {
245                                      continue;
246                                  }
247  
248                                  // Skip bots
249                                  if ($session['TimeF'] == '59:59')
250                                  {
251                                      continue;
252                                  }
253  
254                                  // Generate CRC32 server ID
255                                  $crc32name = crc32(
256                                      $session['Name']
257                                  );
258  
259                                  $player = $entityManagerInterface->getRepository(Player::class)->findOneBy(
260                                      [
261                                          'crc32server' => $server->getCrc32server(),
262                                          'crc32name'   => $crc32name,
263                                      ]
264                                  );
265  
266                                  // Player exists
267                                  if ($player)
268                                  {
269                                      $player->setUpdated(
270                                          time()
271                                      );
272  
273                                      $player->setOnline(
274                                          time()
275                                      );
276  
277                                      if ((int) $session['Frags'] > $player->getFrags())
278                                      {
279                                          $player->setFrags(
280                                              (int) $session['Frags']
281                                          );
282                                      }
283                                  }
284  
285                                  // Create new player
286                                  else
287                                  {
288                                      $player = new Player();
289  
290                                      $player->setCrc32server(
291                                          $server->getCrc32server()
292                                      );
293  
294                                      $player->setCrc32name(
295                                          $crc32name
296                                      );
297  
298                                      $player->setJoined(
299                                          time()
300                                      );
301  
302                                      $player->setUpdated(
303                                          time()
304                                      );
305  
306                                      $player->setOnline(
307                                          time()
308                                      );
309  
310                                      $player->setName(
311                                          (string) $session['Name']
312                                      );
313  
314                                      $player->setFrags(
315                                          (int) $session['Frags']
316                                      );
317                                  }
318  
319                                  // Update DB
320                                  $entityManagerInterface->persist(
321                                      $player
322                                  );
323  
324                                  $entityManagerInterface->flush();
325                              }
326                          }
327                      }
328                  }
329              }
330  
331              catch (Exception $error)
332              {
333                  continue;
334              }
335  
336              catch (\Throwable $error)
337              {
338                  continue;
339              }
340  
341              finally
342              {
343                  $node->Disconnect();
344              }
345          }
346  
347          // Render response
348          return new Response(); // @TODO
349      }
350  }