307 lines
8.4 KiB
Go
307 lines
8.4 KiB
Go
// URTrator - Urban Terror server browser and game launcher, written in
|
|
// Go.
|
|
//
|
|
// Copyright (c) 2016-2020, Stanslav N. a.k.a pztrn (or p0z1tr0n) and
|
|
// URTrator contributors.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject
|
|
// to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be
|
|
// included in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
|
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
package requester
|
|
|
|
import (
|
|
// stdlib
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
// local
|
|
"go.dev.pztrn.name/urtrator/datamodels"
|
|
)
|
|
|
|
type Pooler struct {
|
|
// Maximum number of simultaneous requests running.
|
|
maxrequests int
|
|
// Packet prefix.
|
|
pp string
|
|
// Current requests counter mutex.
|
|
cur_requests_mutex sync.Mutex
|
|
}
|
|
|
|
func (p *Pooler) Initialize() {
|
|
fmt.Println("Initializing requester goroutine pooler...")
|
|
// ToDo: figure out how to make this work nice.
|
|
p.maxrequests = 150
|
|
_ = runtime.GOMAXPROCS(runtime.NumCPU() * 4)
|
|
p.pp = "\377\377\377\377"
|
|
fmt.Println("Pooler initialized")
|
|
}
|
|
|
|
func (p *Pooler) PingOneServer(server_address string) {
|
|
var wait sync.WaitGroup
|
|
|
|
Cache.ServersMutex.Lock()
|
|
server := Cache.Servers[server_address].Server
|
|
Cache.ServersMutex.Unlock()
|
|
|
|
wait.Add(1)
|
|
go func(srv *datamodels.Server) {
|
|
defer wait.Done()
|
|
p.pingServersExecutor(srv)
|
|
}(server)
|
|
wait.Wait()
|
|
}
|
|
|
|
// Servers pinging pooler. Should be started as goroutine to prevent
|
|
// UI blocking.
|
|
func (p *Pooler) PingServers(servers_type string) {
|
|
fmt.Println("About to ping " + servers_type + " servers...")
|
|
|
|
cur_requests := 0
|
|
var wait sync.WaitGroup
|
|
|
|
Cache.ServersMutex.Lock()
|
|
for _, server_to_ping := range Cache.Servers {
|
|
if servers_type == "favorites" && server_to_ping.Server.Favorite != "1" {
|
|
continue
|
|
}
|
|
for {
|
|
p.cur_requests_mutex.Lock()
|
|
if cur_requests == p.maxrequests {
|
|
p.cur_requests_mutex.Unlock()
|
|
time.Sleep(time.Second * 1)
|
|
} else {
|
|
p.cur_requests_mutex.Unlock()
|
|
break
|
|
}
|
|
}
|
|
wait.Add(1)
|
|
p.cur_requests_mutex.Lock()
|
|
cur_requests += 1
|
|
p.cur_requests_mutex.Unlock()
|
|
go func(srv *datamodels.Server) {
|
|
defer wait.Done()
|
|
p.pingServersExecutor(srv)
|
|
p.cur_requests_mutex.Lock()
|
|
cur_requests -= 1
|
|
p.cur_requests_mutex.Unlock()
|
|
}(server_to_ping.Server)
|
|
}
|
|
wait.Wait()
|
|
Cache.ServersMutex.Unlock()
|
|
}
|
|
|
|
func (p *Pooler) pingServersExecutor(server *datamodels.Server) error {
|
|
srv := server.Ip + ":" + server.Port
|
|
fmt.Println("Pinging " + srv)
|
|
// Dial to server.
|
|
start_p := time.Now()
|
|
conn_ping, err2 := net.Dial("udp", srv)
|
|
if err2 != nil {
|
|
fmt.Println("Error dialing to server " + srv + "!")
|
|
return errors.New("Error dialing to server " + srv + "!")
|
|
}
|
|
// Set deadline, so we won't wait forever.
|
|
ddl_ping := time.Now()
|
|
// This should be enough. Maybe, you should'n run URTrator on modem
|
|
// connections? :)
|
|
ddl_ping = ddl_ping.Add(time.Second * 10)
|
|
conn_ping.SetDeadline(ddl_ping)
|
|
|
|
msg_ping := []byte(p.pp + "getinfo")
|
|
conn_ping.Write(msg_ping)
|
|
|
|
// UDP Buffer.
|
|
var received_buf_ping []byte = make([]byte, 128)
|
|
// Received buffer.
|
|
var raw_received_ping []byte
|
|
_, err := conn_ping.Read(received_buf_ping)
|
|
if err != nil {
|
|
fmt.Println("PING ERROR")
|
|
}
|
|
raw_received_ping = append(raw_received_ping, received_buf_ping...)
|
|
conn_ping.Close()
|
|
|
|
delta := strconv.Itoa(int(time.Since(start_p).Nanoseconds()) / 1000000)
|
|
server.Ping = delta
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Pooler) UpdateOneServer(server_address string) {
|
|
var wait sync.WaitGroup
|
|
|
|
Cache.ServersMutex.Lock()
|
|
server := Cache.Servers[server_address].Server
|
|
Cache.ServersMutex.Unlock()
|
|
|
|
wait.Add(1)
|
|
go func(server *datamodels.Server) {
|
|
defer wait.Done()
|
|
p.UpdateSpecificServer(server)
|
|
}(server)
|
|
wait.Wait()
|
|
p.PingOneServer(server_address)
|
|
Eventer.LaunchEvent("flushServers", map[string]string{})
|
|
|
|
Eventer.LaunchEvent("loadAllServers", map[string]string{})
|
|
Eventer.LaunchEvent("loadFavoriteServers", map[string]string{})
|
|
Eventer.LaunchEvent("serversUpdateCompleted", map[string]string{})
|
|
}
|
|
|
|
func (p *Pooler) UpdateServers(servers_type string) {
|
|
var wait sync.WaitGroup
|
|
|
|
Cache.ServersMutex.Lock()
|
|
for _, server := range Cache.Servers {
|
|
if servers_type == "favorites" && server.Server.Favorite != "1" {
|
|
continue
|
|
}
|
|
wait.Add(1)
|
|
go func(server *datamodels.Server) {
|
|
defer wait.Done()
|
|
p.UpdateSpecificServer(server)
|
|
}(server.Server)
|
|
}
|
|
wait.Wait()
|
|
Cache.ServersMutex.Unlock()
|
|
p.PingServers(servers_type)
|
|
Eventer.LaunchEvent("flushServers", map[string]string{})
|
|
|
|
Eventer.LaunchEvent("loadAllServers", map[string]string{})
|
|
Eventer.LaunchEvent("loadFavoriteServers", map[string]string{})
|
|
Eventer.LaunchEvent("serversUpdateCompleted", map[string]string{})
|
|
}
|
|
|
|
// Updates information about specific server.
|
|
func (p *Pooler) UpdateSpecificServer(server *datamodels.Server) error {
|
|
server_addr := server.Ip + ":" + server.Port
|
|
fmt.Println("Updating server: " + server_addr)
|
|
|
|
// Dial to server.
|
|
conn, err1 := net.Dial("udp", server_addr)
|
|
if err1 != nil {
|
|
fmt.Println("Error dialing to server " + server_addr + "!")
|
|
return errors.New("Error dialing to server " + server_addr + "!")
|
|
}
|
|
|
|
// Set deadline, so we won't wait forever.
|
|
ddl := time.Now()
|
|
// This should be enough. Maybe, you should'n run URTrator on modem
|
|
// connections? :)
|
|
ddl = ddl.Add(time.Second * 2)
|
|
conn.SetDeadline(ddl)
|
|
|
|
msg := []byte(p.pp + "getstatus")
|
|
conn.Write(msg)
|
|
|
|
// UDP Buffer.
|
|
var received_buf []byte = make([]byte, 4096)
|
|
// Received buffer.
|
|
var raw_received []byte
|
|
for {
|
|
_, err := conn.Read(received_buf)
|
|
if err != nil {
|
|
break
|
|
}
|
|
raw_received = append(raw_received, received_buf...)
|
|
}
|
|
conn.Close()
|
|
|
|
// First line is "infoResponse" string, which we should skip by
|
|
// splitting response by "\n".
|
|
received_lines := strings.Split(string(raw_received), "\n")
|
|
// We have server's data!
|
|
if len(received_lines) > 1 {
|
|
srv_config := strings.Split(received_lines[1], "\\")
|
|
// Parse server configuration into passed server's datamodel.
|
|
for i := 0; i < len(srv_config); i = i + 1 {
|
|
if srv_config[i] == "g_modversion" {
|
|
server.Version = srv_config[i+1]
|
|
}
|
|
if srv_config[i] == "g_gametype" {
|
|
server.Gamemode = srv_config[i+1]
|
|
}
|
|
if srv_config[i] == "sv_maxclients" {
|
|
server.Maxplayers = srv_config[i+1]
|
|
}
|
|
if srv_config[i] == "clients" {
|
|
server.Players = srv_config[i+1]
|
|
}
|
|
if srv_config[i] == "mapname" {
|
|
server.Map = srv_config[i+1]
|
|
}
|
|
if srv_config[i] == "sv_hostname" {
|
|
server.Name = srv_config[i+1]
|
|
}
|
|
if srv_config[i] == "g_needpass" {
|
|
if srv_config[i+1] == "0" {
|
|
server.IsPrivate = "0"
|
|
} else {
|
|
server.IsPrivate = "1"
|
|
}
|
|
}
|
|
server.ExtendedConfig = received_lines[1]
|
|
}
|
|
if len(received_lines) >= 2 {
|
|
// Here we go, players information.
|
|
players := received_lines[2:]
|
|
var real_players int = 0
|
|
var bots int = 0
|
|
// Calculate players!
|
|
if len(players) == 1 && len(players[0]) > 255 {
|
|
server.Players = "0"
|
|
server.Bots = "0"
|
|
} else {
|
|
// Looks like we have last element to be empty, due to
|
|
// strings.Split() call before.
|
|
for i := range players {
|
|
// Get slice with data for bots-humans parsing.
|
|
player_data := strings.Split(string(players[i]), " ")
|
|
// If slice length isn't equal 3 - this is not what
|
|
// we want.
|
|
if len(player_data) != 3 {
|
|
continue
|
|
}
|
|
|
|
if player_data[1] == "0" {
|
|
bots++
|
|
} else {
|
|
real_players++
|
|
}
|
|
}
|
|
//server.Players = strconv.Itoa(len(players) - 1)
|
|
server.Players = strconv.Itoa(real_players)
|
|
server.Bots = strconv.Itoa(bots)
|
|
fmt.Println(server.Players, server.Bots)
|
|
}
|
|
server.PlayersInfo = strings.Join(received_lines[2:], "\\")
|
|
}
|
|
}
|
|
|
|
// ToDo: Calculate ping. 0 for now.
|
|
server.Ping = "0"
|
|
return nil
|
|
}
|