clientRequest = $this->setCurrentRequest($request); $this->request = $request; if (!$this->isTorrentClient()) { return false; } $this->isFirewalled(); $this->user = $this->findUserTask->byPid($this->clientRequest->pid, ['profile']); $this->torrent = $this->findTorrentTask->byInfoHash($this->clientRequest->infoHash); $this->peer = $this->findPeerTask->byPeerIdAndTorrentUuid($this->clientRequest->peerId, $this->torrent->uuid); if (is_null($this->peer)) { $this->peer = $this->storePeerTask->run($this->preparePeerData()); } switch ($this->clientRequest->event) { case 'started': $this->started(); break; case 'stopped': $this->stopped(); break; case 'finished': $this->finished(); break; case empty($this->currentRequest->event): $this->noEvent(); break; } return Bencode::encode([ 'interval' => config('tracker.max_announce'), 'min interval' => config('tracker.max_announce'), 'complete' => $this->torrent->seeder, 'incomplete' => $this->torrent->leecher, 'peers' => (function($peerReturn) { if($this->clientRequest->compact) { return $peerReturn; } $peers = []; $peerReturn->map(function(Peer $peer) use(&$peers) { $peers[] = [ 'peer id' => $peer->peer_id, 'ip' => $peer->ip, 'port' => $peer->port ]; }); return $peers; })($this->sendRandomPeers()) ]); } /** * @return bool */ private function isTorrentClient(): bool { return !preg_match('/' . $this->browsers . '/', $this->clientRequest->agent); } /** * */ public function isFirewalled(): void { $theError = ''; $fd = @fsockopen($this->clientRequest->ip, $this->clientRequest->port, $errno, $theError, 10); $this->clientRequest->nat = !$fd; if($fd) fclose($fd); } /** * @return void */ private function started(): void { $this->updatePeerTask->run($this->peer, $this->preparePeerData('started')); $this->updateTorrentTask->run($this->torrent, $this->prepareTorrentData('started')); $this->updateProfileTask->run($this->user->profile, $this->prepareProfileData()); } /** * @return void * @throws Exception */ private function stopped(): void { $this->destroyPeerTask->run($this->peer); $this->updateTorrentTask->run($this->torrent, $this->prepareTorrentData('stopped')); $this->updateProfileTask->run($this->user->profile, $this->prepareProfileData()); } /** * @return void */ private function finished(): void { $this->updatePeerTask->run($this->peer, $this->preparePeerData('finished')); $this->updateTorrentTask->run($this->torrent, $this->prepareTorrentData('finished')); $this->updateProfileTask->run($this->user->profile, $this->prepareProfileData()); } /** * @return void */ private function noEvent(): void { if ($this->peer->bytes != 0 && $this->clientRequest->left == 0) { $this->updatePeerTask->run($this->peer, $this->preparePeerData('finished')); $this->updateTorrentTask->run($this->torrent, $this->prepareTorrentData('finished')); } $this->collectBytes(); } /** * @return void */ private function collectBytes(): void { $diff = bcsub($this->peer->bytes, $this->clientRequest->left); $this->updatePeerTask->run($this->peer, $this->preparePeerData('bytes', $diff)); } /** * @return string|null|Collection */ private function sendRandomPeers(): Collection|string|null { $peerCollection = $this->findPeerTask->byTorrentUuid($this->torrent->uuid); if (!is_null($peerCollection)) { if($this->clientRequest->compact) { $compactStr = ''; $peerCollection->map(function (Peer $peer) use (&$compactStr) { if($peer->created_uuid == $this->user->uuid) return; $compactStr .= str_pad(pack('Nn', ip2long($peer->ip), $peer->port), 6); }); return $compactStr; } return $peerCollection; } return null; } /** * @param string $status * @param float $diff * @return array */ private function preparePeerData(string $status = '', float $diff = 0): array { $data = [ 'created_uuid' => $this->user->uuid, 'updated_uuid' => $this->user->uuid, 'torrent_uuid' => $this->torrent->uuid, 'peer_id' => $this->clientRequest->peerId, 'ip' => $this->clientRequest->ip, 'port' => $this->clientRequest->port, 'status' => $this->clientRequest->left == 0 ? 1 : 2, 'natuser' => $this->clientRequest->nat, 'client' => $this->clientRequest->agent, 'dns' => $this->clientRequest->dns, 'downloaded' => $this->clientRequest->downloaded, 'uploaded' => $this->clientRequest->uploaded, ]; switch ($status) { case 'started': unset($data['peer_id'], $data['torrent_uuid'], $data['ip'], $data['port'], $data['natuser'], $data['client'], $data['dns']); break; case 'finished': unset($data['peer_id'], $data['torrent_uuid'], $data['ip'], $data['port'], $data['natuser'], $data['client'], $data['dns'], $data['compact']); $data['bytes'] = 0; $data['status'] = 'seeder'; break; case 'bytes': unset($data['peer_id'], $data['torrent_uuid'], $data['ip'], $data['port'], $data['status'], $data['natuser'], $data['client'], $data['dns'], $data['compact']); $data['bytes'] = empty($diff) ? $this->peer->bytes : $this->clientRequest->left; break; } return $data; } /** * @param string $status * @return array */ private function prepareTorrentData(string $status = ''): array { switch ($status) { case 'started': $this->clientRequest->left > 0 ? $this->torrent->leecher++ : $this->torrent->seeder++; return [ 'leecher' => $this->torrent->leecher, 'seeder' => $this->torrent->seeder, 'finished' => $this->torrent->finished, ]; case 'finished': return [ 'leecher' => $this->torrent->leecher--, 'seeder' => $this->torrent->seeder++, 'finished' => $this->torrent->finished++, ]; case 'stopped': $this->clientRequest->left > 0 ? $this->torrent->leecher-- : $this->torrent->seeder--; return [ 'leecher' => $this->torrent->leecher, 'seeder' => $this->torrent->seeder, 'finished' => $this->torrent->finished, ]; } return []; } /** * @return array */ #[ArrayShape([ 'uploaded' => "mixed", 'downloaded' => "mixed", 'ratio' => "float|int" ])] private function prepareProfileData(): array { $uploaded = $this->user->profile->uploaded + $this->clientRequest->uploaded; $downloaded = $this->user->profile->downloaded + $this->clientRequest->downloaded; return [ 'uploaded' => $uploaded, 'downloaded' => $downloaded, 'ratio' => $uploaded == 0 ? 0 : ($downloaded / $uploaded) ]; } }