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 }