diff --git a/cache/cache_object.go b/cache/cache_object.go new file mode 100644 index 0000000..01a5508 --- /dev/null +++ b/cache/cache_object.go @@ -0,0 +1,132 @@ +// URTator - Urban Terror server browser and game launcher, written in +// Go. +// +// Copyright (c) 2016, Stanslav N. a.k.a pztrn (or p0z1tr0n) +// All rights reserved. +// +// Licensed under Terms and Conditions of GNU General Public License +// version 3 or any higher. +// ToDo: put full text of license here. +package cache + +import ( + // stdlib + "fmt" + + // local + "github.com/pztrn/urtrator/cachemodels" + "github.com/pztrn/urtrator/datamodels" +) + +type Cache struct { + // Servers cache. + Servers map[string]*cachemodels.Server +} + +func (c *Cache) CreateServer(addr string) { + _, ok := c.Servers[addr] + if !ok { + fmt.Println("Creating cached server " + addr) + c.Servers[addr] = &cachemodels.Server{} + c.Servers[addr].Server = &datamodels.Server{} + } else { + fmt.Println("Server " + addr + " already exist.") + } +} + +// Flush servers to database. +func (c *Cache) FlushServers() { + fmt.Println("Updating servers information in database...") + raw_cached := []datamodels.Server{} + Database.Db.Select(&raw_cached, "SELECT * FROM servers") + + // Create map[string]*datamodels.Server once, so we won't iterate + // over slice of datamodels.Server everytime. + cached_servers := make(map[string]*datamodels.Server) + for s := range raw_cached { + mapping_item_name := raw_cached[s].Ip + ":" + raw_cached[s].Port + cached_servers[mapping_item_name] = &raw_cached[s] + } + + new_servers := make(map[string]*datamodels.Server) + + // Update our cached mapping. + for _, s := range c.Servers { + mapping_item_name := s.Server.Ip + ":" + s.Server.Port + _, ok := cached_servers[mapping_item_name] + if !ok { + fmt.Println(mapping_item_name + " not found!") + new_servers[mapping_item_name] = s.Server + } else { + cached_servers[mapping_item_name].Ip = s.Server.Ip + cached_servers[mapping_item_name].Port = s.Server.Port + cached_servers[mapping_item_name].Name = s.Server.Name + cached_servers[mapping_item_name].Players = s.Server.Players + cached_servers[mapping_item_name].Maxplayers = s.Server.Maxplayers + cached_servers[mapping_item_name].Ping = s.Server.Ping + cached_servers[mapping_item_name].Map = s.Server.Map + cached_servers[mapping_item_name].Gamemode = s.Server.Gamemode + cached_servers[mapping_item_name].Version = s.Server.Version + cached_servers[mapping_item_name].ExtendedConfig = s.Server.ExtendedConfig + cached_servers[mapping_item_name].PlayersInfo = s.Server.PlayersInfo + } + } + + tx := Database.Db.MustBegin() + fmt.Println("Adding new servers...") + for _, srv := range new_servers { + tx.NamedExec("INSERT INTO servers (ip, port, name, ping, players, maxplayers, gamemode, map, version, extended_config, players_info) VALUES (:ip, :port, :name, :ping, :players, :maxplayers, :gamemode, :map, :version, :extended_config, :players_info)", srv) + } + fmt.Println("Updating cached servers...") + for _, srv := range cached_servers { + tx.NamedExec("UPDATE servers SET name=:name, players=:players, maxplayers=:maxplayers, gamemode=:gamemode, map=:map, ping=:ping, version=:version, extended_config=:extended_config, players_info=:players_info WHERE ip=:ip AND port=:port", &srv) + } + + tx.Commit() + fmt.Println("Done") +} + +func (c *Cache) Initialize() { + fmt.Println("Initializing cache...") + c.initializeStorages() + c.LoadServers() + + Eventer.AddEventHandler("flushServers", c.FlushServers) + Eventer.AddEventHandler("loadServersIntoCache", c.LoadServers) +} + +func (c *Cache) initializeStorages() { + // Servers cache. + c.Servers = make(map[string]*cachemodels.Server) +} + +func (c *Cache) LoadServers() { + c.Servers = make(map[string]*cachemodels.Server) + // Getting servers from database. + raw_servers := []datamodels.Server{} + err := Database.Db.Select(&raw_servers, "SELECT * FROM servers") + if err != nil { + fmt.Println(err.Error()) + } + + // Due to nature of pointers and goroutines thing (?) this should + // be done in this way. + for _, server := range raw_servers { + key := server.Ip + ":" + server.Port + c.CreateServer(key) + c.Servers[key].Server.Name = server.Name + c.Servers[key].Server.Ip = server.Ip + c.Servers[key].Server.Port = server.Port + c.Servers[key].Server.Players = server.Players + c.Servers[key].Server.Maxplayers = server.Maxplayers + c.Servers[key].Server.Ping = server.Ping + c.Servers[key].Server.Gamemode = server.Gamemode + c.Servers[key].Server.Map = server.Map + c.Servers[key].Server.Version = server.Version + c.Servers[key].Server.Favorite = server.Favorite + c.Servers[key].Server.Password = server.Password + c.Servers[key].Server.ProfileToUse = server.ProfileToUse + c.Servers[key].Server.ExtendedConfig = server.ExtendedConfig + c.Servers[key].Server.PlayersInfo = server.PlayersInfo + } +} diff --git a/cache/exported.go b/cache/exported.go new file mode 100644 index 0000000..55e88dc --- /dev/null +++ b/cache/exported.go @@ -0,0 +1,28 @@ +// URTator - Urban Terror server browser and game launcher, written in +// Go. +// +// Copyright (c) 2016, Stanslav N. a.k.a pztrn (or p0z1tr0n) +// All rights reserved. +// +// Licensed under Terms and Conditions of GNU General Public License +// version 3 or any higher. +// ToDo: put full text of license here. +package cache + +import ( + // local + event "github.com/pztrn/urtrator/eventer" + "github.com/pztrn/urtrator/database" +) + +var ( + Database *database.Database + Eventer *event.Eventer +) + +func New(d *database.Database, e *event.Eventer) *Cache { + Database = d + Eventer = e + c := Cache{} + return &c +} diff --git a/cachemodels/server.go b/cachemodels/server.go new file mode 100644 index 0000000..91939c4 --- /dev/null +++ b/cachemodels/server.go @@ -0,0 +1,24 @@ +// URTator - Urban Terror server browser and game launcher, written in +// Go. +// +// Copyright (c) 2016, Stanslav N. a.k.a pztrn (or p0z1tr0n) +// All rights reserved. +// +// Licensed under Terms and Conditions of GNU General Public License +// version 3 or any higher. +// ToDo: put full text of license here. +package cachemodels + +import ( + // local + "github.com/pztrn/urtrator/datamodels" + + // Other + "github.com/mattn/go-gtk/gtk" +) + +type Server struct { + Server *datamodels.Server + TreeIter gtk.TreeIter + IterSet bool +} diff --git a/context/context_object.go b/context/context_object.go index 01c9736..eb94d33 100644 --- a/context/context_object.go +++ b/context/context_object.go @@ -14,6 +14,7 @@ import ( "fmt" // local + "github.com/pztrn/urtrator/cache" "github.com/pztrn/urtrator/colorizer" "github.com/pztrn/urtrator/configuration" "github.com/pztrn/urtrator/database" @@ -26,6 +27,8 @@ import ( ) type Context struct { + // Caching. + Cache *cache.Cache // Colors parser and prettifier. Colorizer *colorizer.Colorizer // Configuration. @@ -43,12 +46,18 @@ type Context struct { func (ctx *Context) Close() { fmt.Println("Closing URTrator...") + ctx.Cache.FlushServers() ctx.Database.Close() // At last, close main window. gtk.MainQuit() } +func (ctx *Context) initializeCache() { + ctx.Cache = cache.New(ctx.Database, ctx.Eventer) + ctx.Cache.Initialize() +} + func (ctx *Context) initializeColorizer() { ctx.Colorizer = colorizer.New() ctx.Colorizer.Initialize() @@ -76,7 +85,7 @@ func (ctx *Context) initializeLauncher() { } func (ctx *Context) initializeRequester() { - ctx.Requester = requester.New() + ctx.Requester = requester.New(ctx.Cache, ctx.Eventer) ctx.Requester.Initialize() } @@ -86,6 +95,7 @@ func (ctx *Context) Initialize() { ctx.initializeConfig() ctx.initializeDatabase() ctx.initializeEventer() + ctx.initializeCache() ctx.initializeLauncher() ctx.initializeRequester() } diff --git a/requester/exported.go b/requester/exported.go index 1c2a64a..67c8bdf 100644 --- a/requester/exported.go +++ b/requester/exported.go @@ -12,9 +12,20 @@ package requester import ( // stdlib "fmt" + + // local + "github.com/pztrn/urtrator/cache" + "github.com/pztrn/urtrator/eventer" ) -func New() *Requester { +var ( + Cache *cache.Cache + Eventer *eventer.Eventer +) + +func New(c *cache.Cache, e *eventer.Eventer) *Requester { + Cache = c + Eventer = e fmt.Println("Creating Requester object...") r := Requester{} return &r diff --git a/requester/pooler.go b/requester/pooler.go new file mode 100644 index 0000000..69a095b --- /dev/null +++ b/requester/pooler.go @@ -0,0 +1,209 @@ +// URTator - Urban Terror server browser and game launcher, written in +// Go. +// +// Copyright (c) 2016, Stanslav N. a.k.a pztrn (or p0z1tr0n) +// All rights reserved. +// +// Licensed under Terms and Conditions of GNU General Public License +// version 3 or any higher. +// ToDo: put full text of license here. +package requester + +import ( + // stdlib + "errors" + "fmt" + "net" + "runtime" + "strconv" + "strings" + "sync" + "time" + + // local + "github.com/pztrn/urtrator/datamodels" +) + +type Pooler struct { + // Maximum number of simultaneous requests running. + maxrequests int + // Packet prefix. + pp string +} + +func (p *Pooler) Initialize() { + fmt.Println("Initializing requester goroutine pooler...") + p.maxrequests = runtime.NumCPU() * 2 + p.pp = "\377\377\377\377" +} + +// 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 + + for _, server_to_ping := range Cache.Servers { + if servers_type == "favorites" && server_to_ping.Server.Favorite != "1" { + continue + } + for { + if cur_requests == p.maxrequests { + time.Sleep(time.Second * 1) + } else { + break + } + } + wait.Add(1) + cur_requests += 1 + go func(srv *datamodels.Server) { + defer wait.Done() + p.pingServersExecutor(srv) + cur_requests -= 1 + }(server_to_ping.Server) + } + wait.Wait() +} + +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) + fmt.Println(delta) + server.Ping = delta + + return nil +} + +func (p *Pooler) UpdateServers(servers_type string) { + var wait sync.WaitGroup + + 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() + Eventer.LaunchEvent("flushServers") + p.PingServers(servers_type) + + if servers_type == "all" { + Eventer.LaunchEvent("loadAllServers") + } else if servers_type == "favorites" { + Eventer.LaunchEvent("loadFavoriteServers") + } +} + +// 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] + } + server.ExtendedConfig = received_lines[1] + } + if len(received_lines) >= 2 { + // Here we go, players information. + players := received_lines[2:] + server.Players = strconv.Itoa(len(players)) + //server.PlayersInfo = received_lines[2:] + } + } + + // ToDo: Calculate ping. 0 for now. + server.Ping = "0" + + fmt.Println(server) + + return nil +} diff --git a/requester/requester_object.go b/requester/requester_object.go index 12a8e0f..3c5b8b1 100644 --- a/requester/requester_object.go +++ b/requester/requester_object.go @@ -12,19 +12,15 @@ package requester import ( // stdlib "bytes" - "errors" "fmt" "net" "strconv" - "strings" - "sync" "time" - - // local - "github.com/pztrn/urtrator/datamodels" ) type Requester struct { + // Pooler. + pooler *Pooler // Master server address master_server string // Master server port @@ -44,12 +40,14 @@ func (r *Requester) Initialize() { r.master_server_port = "27900" r.pp = "\377\377\377\377" r.ip_delimiter = 92 + r.pooler = &Pooler{} + r.pooler.Initialize() } // Gets all available servers from master server. -func (r *Requester) getServers(callback chan [][]string) { +// This isn't in pooler, because it have no need to be pooled. +func (r *Requester) getServers() { // IP addresses we will compose to return. - var received_ips [][]string conn, err1 := net.Dial("udp", r.master_server + ":" + r.master_server_port) if err1 != nil { fmt.Println("Error dialing to master server!") @@ -109,146 +107,29 @@ func (r *Requester) getServers(callback chan [][]string) { // second byte. p1 := int(slice[4]) * 256 port := strconv.Itoa(p1 + int(slice[5])) + addr := ip + ":" + port - // Create a slice with IP and port. - ip_and_port := []string{ip, port} - // Add it to received_ips. - received_ips = append(received_ips, ip_and_port) + // Check if we already have this server added previously. If so - do nothing. + _, ok := Cache.Servers[addr] + if !ok { + // Create cached server. + Cache.CreateServer(addr) + Cache.Servers[addr].Server.Ip = ip + Cache.Servers[addr].Server.Port = port + } } - - fmt.Println("Parsed " + strconv.Itoa(len(received_ips)) + " addresses") - callback <- received_ips } // Updates information about all available servers from master server and // parses it to usable format. -func (r *Requester) UpdateAllServers(done_chan chan map[string]*datamodels.Server, error_chan chan bool) { +func (r *Requester) UpdateAllServers() { fmt.Println("Starting all servers updating procedure...") - - callback := make(chan [][]string) - go r.getServers(callback) - - servers := make(map[string]*datamodels.Server) - - select { - case data := <- callback: - // Yay, we got data! :) - fmt.Println("Received " + strconv.Itoa(len(data)) + " servers") - servers = r.updateServerGoroutineDispatcher(data) - break - case <- time.After(time.Second * 10): - // Timeouted? Okay, push error back. - error_chan <- true - } - - done_chan <- servers + r.getServers() + r.pooler.UpdateServers("all") } -func (r *Requester) UpdateFavoriteServers(servers [][]string, done_chan chan map[string]*datamodels.Server, error_chan chan bool) { +func (r *Requester) UpdateFavoriteServers() { fmt.Println("Updating favorites servers...") - updated_servers := r.updateServerGoroutineDispatcher(servers) - done_chan <- updated_servers + r.pooler.UpdateServers("favorites") } -func (r *Requester) updateServerGoroutineDispatcher(data [][]string) map[string]*datamodels.Server { - var wait sync.WaitGroup - var lock = sync.RWMutex{} - done_updating := 0 - servers := make(map[string]*datamodels.Server) - - for _, s := range data { - s := datamodels.Server{ - Ip: s[0], - Port: s[1], - } - go func(s *datamodels.Server, servers map[string]*datamodels.Server) { - wait.Add(1) - defer wait.Done() - r.UpdateServer(s) - done_updating = done_updating + 1 - lock.Lock() - servers[s.Ip + ":" + s.Port] = s - lock.Unlock() - }(&s, servers) - } - wait.Wait() - return servers -} - -// Updates information about specific server. -func (r *Requester) UpdateServer(server *datamodels.Server) error { - srv := server.Ip + ":" + server.Port - fmt.Println("Updating server: " + srv) - - // Dial to server. - conn, err1 := net.Dial("udp", srv) - if err1 != nil { - fmt.Println("Error dialing to server " + srv + "!") - return errors.New("Error dialing to server " + srv + "!") - } - defer conn.Close() - - // 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(r.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...) - } - - // 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 len(received_lines) >= 2 { - // Here we go, players information. - players := received_lines[2:] - server.Players = strconv.Itoa(len(players)) - } - } - - // ToDo: Calculate ping. 0 for now. - server.Ping = "0" - // ToDo: put this info. - server.ExtendedConfig = "" - server.PlayersInfo = "" - - return nil -} diff --git a/ui/mainwindow.go b/ui/mainwindow.go index b05ce0b..84fb7bb 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -14,6 +14,7 @@ import ( "encoding/base64" "errors" "fmt" + "reflect" "runtime" "strconv" "strings" @@ -148,10 +149,15 @@ func (m *MainWindow) addToFavorites() { } } +// Executes when delimiter for two panes is moved, to calculate VALID +// position. func (m *MainWindow) checkMainPanePosition() { m.pane_negative_position = m.window_width - m.hpane.GetPosition() } +// Executes when main window is moved or resized. +// Also calculating pane delimiter position and set it to avoid +// widgets hell :). func (m *MainWindow) checkPositionAndSize() { m.window.GetPosition(&m.window_pos_x, &m.window_pos_y) m.window.GetSize(&m.window_width, &m.window_height) @@ -159,6 +165,7 @@ func (m *MainWindow) checkPositionAndSize() { m.hpane.SetPosition(m.window_width - m.pane_negative_position) } +// Executes on URTrator shutdown. func (m *MainWindow) Close() { // Save window parameters. ctx.Cfg.Cfg["/mainwindow/width"] = strconv.Itoa(m.window_width) @@ -170,6 +177,7 @@ func (m *MainWindow) Close() { ctx.Close() } +// Deleting server from favorites. func (m *MainWindow) deleteFromFavorites() { fmt.Println("Removing server from favorites...") current_tab := m.tab_widget.GetTabLabelText(m.tab_widget.GetNthPage(m.tab_widget.GetCurrentPage())) @@ -221,6 +229,8 @@ func (m *MainWindow) deleteFromFavorites() { } } +// Drop database data. +// ToDo: extend so we should have an ability to decide what to drop. func (m *MainWindow) dropDatabasesData() { fmt.Println("Dropping database data...") var will_continue bool = false @@ -249,6 +259,7 @@ func (m *MainWindow) dropDatabasesData() { } } +// Executes on "Edit favorite server" click. func (m *MainWindow) editFavorite() { fmt.Println("Editing favorite server...") @@ -276,6 +287,8 @@ func (m *MainWindow) editFavorite() { } } +// Executes when "Hide offline servers" checkbox changed it's state on +// "Servers" tab. func (m *MainWindow) hideOfflineAllServers() { fmt.Println("(Un)Hiding offline servers in 'Servers' tab...") if m.all_servers_hide_offline.GetActive() { @@ -286,6 +299,8 @@ func (m *MainWindow) hideOfflineAllServers() { ctx.Eventer.LaunchEvent("loadAllServers") } +// Executes when "Hide offline servers" checkbox changed it's state on +// "Favorites" tab. func (m *MainWindow) hideOfflineFavoriteServers() { fmt.Println("(Un)Hiding offline servers in 'Favorite' tab...") if m.fav_servers_hide_offline.GetActive() { @@ -296,6 +311,7 @@ func (m *MainWindow) hideOfflineFavoriteServers() { ctx.Eventer.LaunchEvent("loadFavoriteServers") } +// Main window initialization. func (m *MainWindow) Initialize() { m.initializeStorages() @@ -381,6 +397,9 @@ func (m *MainWindow) Initialize() { m.initializeTrayIcon() } + // Events. + m.initializeEvents() + // Game profiles and launch button. profile_and_launch_hbox := gtk.NewHBox(false, 0) m.vbox.PackStart(profile_and_launch_hbox, false, true, 5) @@ -421,12 +440,22 @@ func (m *MainWindow) Initialize() { // Launch events. ctx.Eventer.LaunchEvent("loadProfiles") + ctx.Eventer.LaunchEvent("loadServersIntoCache") ctx.Eventer.LaunchEvent("loadAllServers") ctx.Eventer.LaunchEvent("loadFavoriteServers") gtk.Main() } +// Events initialization. +func (m *MainWindow) initializeEvents() { + fmt.Println("Initializing events...") + ctx.Eventer.AddEventHandler("loadAllServers", m.loadAllServers) + ctx.Eventer.AddEventHandler("loadFavoriteServers", m.loadFavoriteServers) + ctx.Eventer.AddEventHandler("serversUpdateCompleted", m.serversUpdateCompleted) +} + +// Main menu initialization. func (m *MainWindow) InitializeMainMenu() { m.menubar = gtk.NewMenuBar() m.vbox.PackStart(m.menubar, false, false, 0) @@ -472,6 +501,7 @@ func (m *MainWindow) InitializeMainMenu() { about_menu_drop_database_data_item.Connect("activate", m.dropDatabasesData) } +// Sidebar (with quick connect and server's information) initialization. func (m *MainWindow) initializeSidebar() { sidebar_vbox := gtk.NewVBox(false, 0) @@ -544,6 +574,7 @@ func (m *MainWindow) initializeStorages() { m.hidden = false } +// Tabs widget initialization, including all child widgets. func (m *MainWindow) InitializeTabs() { // Create tabs widget. m.tab_widget = gtk.NewNotebook() @@ -667,11 +698,9 @@ func (m *MainWindow) InitializeTabs() { // Add tab_widget widget to window. m.hpane.Add1(m.tab_widget) - - ctx.Eventer.AddEventHandler("loadAllServers", m.loadAllServers) - ctx.Eventer.AddEventHandler("loadFavoriteServers", m.loadFavoriteServers) } +// Toolbar initialization. func (m *MainWindow) InitializeToolbar() { m.toolbar = gtk.NewToolbar() m.vbox.PackStart(m.toolbar, false, false, 5) @@ -708,6 +737,7 @@ func (m *MainWindow) InitializeToolbar() { m.toolbar.Insert(fav_delete_button, 4) } +// Tray icon initialization. func (m *MainWindow) initializeTrayIcon() { fmt.Println("Initializing tray icon...") @@ -859,67 +889,68 @@ func (m *MainWindow) launchGame() error { } func (m *MainWindow) loadAllServers() { - fmt.Println("Loading servers lists into widgets...") - servers := []datamodels.Server{} - err := ctx.Database.Db.Select(&servers, "SELECT * FROM servers") - if err != nil { - fmt.Println(err.Error()) - } + fmt.Println("Loading all servers...") // ToDo: do it without clearing. - m.all_servers_store.Clear() - for _, srv := range servers { - if m.all_servers_hide_offline.GetActive() && srv.Name == "" && srv.Players == "" { + srv_addrs := reflect.ValueOf(ctx.Cache.Servers).MapKeys() + fmt.Println(srv_addrs) + for _, server := range ctx.Cache.Servers { + if m.all_servers_hide_offline.GetActive() && server.Server.Name == "" && server.Server.Players == "" { continue } var iter gtk.TreeIter - m.all_servers_store.Append(&iter) - if srv.Name == "" && srv.Players == "" { + if !server.IterSet { + server.TreeIter = iter + m.all_servers_store.Append(&iter) + server.IterSet = true + } else { + iter = server.TreeIter + } + if server.Server.Name == "" && server.Server.Players == "" { m.all_servers_store.Set(&iter, 0, gtk.NewImage().RenderIcon(gtk.STOCK_NO, gtk.ICON_SIZE_SMALL_TOOLBAR, "").GPixbuf) } else { m.all_servers_store.Set(&iter, 0, gtk.NewImage().RenderIcon(gtk.STOCK_OK, gtk.ICON_SIZE_SMALL_TOOLBAR, "").GPixbuf) } - srv_name := ctx.Colorizer.Fix(srv.Name) - m.all_servers_store.Set(&iter, 1, srv_name) - m.all_servers_store.Set(&iter, 2, m.gamemodes[srv.Gamemode]) - m.all_servers_store.Set(&iter, 3, srv.Map) - m.all_servers_store.Set(&iter, 4, srv.Players + "/" + srv.Maxplayers) - m.all_servers_store.Set(&iter, 5, srv.Ping) - m.all_servers_store.Set(&iter, 6, srv.Version) - m.all_servers_store.Set(&iter, 7, srv.Ip + ":" + srv.Port) + server_name := ctx.Colorizer.Fix(server.Server.Name) + m.all_servers_store.Set(&iter, 1, server_name) + m.all_servers_store.Set(&iter, 2, m.gamemodes[server.Server.Gamemode]) + m.all_servers_store.Set(&iter, 3, server.Server.Map) + m.all_servers_store.Set(&iter, 4, server.Server.Players + "/" + server.Server.Maxplayers) + m.all_servers_store.Set(&iter, 5, server.Server.Ping) + m.all_servers_store.Set(&iter, 6, server.Server.Version) + m.all_servers_store.Set(&iter, 7, server.Server.Ip + ":" + server.Server.Port) } } func (m *MainWindow) loadFavoriteServers() { fmt.Println("Loading favorite servers...") - servers := []datamodels.Server{} - err := ctx.Database.Db.Select(&servers, "SELECT * FROM servers WHERE favorite='1'") - if err != nil { - fmt.Println(err.Error()) - } - // ToDo: do it without clearing. - m.fav_servers_store.Clear() - for _, srv := range servers { - if srv.Favorite != "1" { + for _, server := range ctx.Cache.Servers { + if server.Server.Favorite != "1" { continue } - if m.fav_servers_hide_offline.GetActive() && srv.Name == "" && srv.Players == "" { + if m.fav_servers_hide_offline.GetActive() && server.Server.Name == "" && server.Server.Players == "" { continue } var iter gtk.TreeIter - m.fav_servers_store.Append(&iter) - if srv.Name == "" && srv.Players == "" { + if !server.IterSet { + server.TreeIter = iter + m.fav_servers_store.Append(&iter) + server.IterSet = true + } else { + iter = server.TreeIter + } + if server.Server.Name == "" && server.Server.Players == "" { m.fav_servers_store.Set(&iter, 0, gtk.NewImage().RenderIcon(gtk.STOCK_NO, gtk.ICON_SIZE_SMALL_TOOLBAR, "").GPixbuf) } else { m.fav_servers_store.Set(&iter, 0, gtk.NewImage().RenderIcon(gtk.STOCK_OK, gtk.ICON_SIZE_SMALL_TOOLBAR, "").GPixbuf) } - srv_name := ctx.Colorizer.Fix(srv.Name) - m.fav_servers_store.Set(&iter, 1, srv_name) - m.fav_servers_store.Set(&iter, 2, m.gamemodes[srv.Gamemode]) - m.fav_servers_store.Set(&iter, 3, srv.Map) - m.fav_servers_store.Set(&iter, 4, srv.Players + "/" + srv.Maxplayers) - m.fav_servers_store.Set(&iter, 5, srv.Ping) - m.fav_servers_store.Set(&iter, 6, srv.Version) - m.fav_servers_store.Set(&iter, 7, srv.Ip + ":" + srv.Port) + server_name := ctx.Colorizer.Fix(server.Server.Name) + m.fav_servers_store.Set(&iter, 1, server_name) + m.fav_servers_store.Set(&iter, 2, m.gamemodes[server.Server.Gamemode]) + m.fav_servers_store.Set(&iter, 3, server.Server.Map) + m.fav_servers_store.Set(&iter, 4, server.Server.Players + "/" + server.Server.Maxplayers) + m.fav_servers_store.Set(&iter, 5, server.Server.Ping) + m.fav_servers_store.Set(&iter, 6, server.Server.Version) + m.fav_servers_store.Set(&iter, 7, server.Server.Ip + ":" + server.Server.Port) } } @@ -944,6 +975,10 @@ func (m *MainWindow) loadProfiles() { fmt.Println("Added " + strconv.Itoa(m.old_profiles_count) + " profiles") } +func (m *MainWindow) serversUpdateCompleted() { + m.statusbar.Push(m.statusbar_context_id, "Servers updated.") +} + func (m *MainWindow) showHide() { if m.hidden { m.window.Show() @@ -966,47 +1001,15 @@ func (m *MainWindow) unlockInterface() { m.statusbar.Push(m.statusbar_context_id, "URTrator is ready.") } -func (m *MainWindow) updateFavorites(done_chan chan map[string]*datamodels.Server, error_chan chan bool) { - m.fav_servers_store.Clear() - servers := []datamodels.Server{} - err := ctx.Database.Db.Select(&servers, "SELECT * FROM servers WHERE favorite='1'") - if err != nil { - fmt.Println(err.Error()) - } - - var servers_from_db [][]string - - for s := range servers { - servers_from_db = append(servers_from_db, []string{servers[s].Ip, servers[s].Port}) - } - - go ctx.Requester.UpdateFavoriteServers(servers_from_db, done_chan, error_chan) -} - +// Triggered when "Update all servers" button is clicked. func (m *MainWindow) UpdateServers() { m.statusbar.Push(m.statusbar_context_id, "Updating servers...") current_tab := m.tab_widget.GetTabLabelText(m.tab_widget.GetNthPage(m.tab_widget.GetCurrentPage())) fmt.Println("Updating servers on tab '" + current_tab + "'...") - done_chan := make(chan map[string]*datamodels.Server, 1) - error_chan := make(chan bool, 1) + if strings.Contains(current_tab, "Servers") { - go ctx.Requester.UpdateAllServers(done_chan, error_chan) + go ctx.Requester.UpdateAllServers() } else if strings.Contains(current_tab, "Favorites") { - m.updateFavorites(done_chan, error_chan) + go ctx.Requester.UpdateFavoriteServers() } - - select { - case data := <- done_chan: - fmt.Println("Information about servers successfully gathered") - ctx.Database.UpdateServers(data) - if strings.Contains(current_tab, "Servers") { - ctx.Eventer.LaunchEvent("loadAllServers") - } else if strings.Contains(current_tab, "Favorites") { - ctx.Eventer.LaunchEvent("loadFavoriteServers") - } - case <- error_chan: - fmt.Println("Error occured") - } - - m.statusbar.Push(m.statusbar_context_id, "Servers updated.") } diff --git a/urtrator.go b/urtrator.go index 61a79dc..ca895a5 100644 --- a/urtrator.go +++ b/urtrator.go @@ -16,11 +16,15 @@ import ( // stdlib "fmt" + "runtime" ) func main() { fmt.Println("This is URTrator, version 0.1") + numCPUs := runtime.NumCPU() + runtime.GOMAXPROCS(numCPUs) + ctx := context.New() ctx.Initialize()