diff --git a/README.md b/README.md index c543d6f..1e67e49 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,14 @@ updating information about them. * Extended Urban Terror launching capabilities (e.g. launching game in another X session). * Supporting of multiple profiles with multiple game versions. +When you're launching Urban Terror URTrator will check profile you're +trying to use and, if version incompatability found, will not launch +the game. +* Favorites servers. Planning: * Updating single server. -* Favorites servers. * Showing information about servers (like in UrT Connector). * Friends searching. * RCON console. diff --git a/database/database_object.go b/database/database_object.go index f06726c..7d50ca8 100644 --- a/database/database_object.go +++ b/database/database_object.go @@ -65,12 +65,48 @@ func (d *Database) Migrate() { func (d *Database) UpdateServers(data map[string]*datamodels.Server) { fmt.Println("Updating servers information in database...") - // ToDo: real update :) - d.Db.MustExec("DELETE FROM servers") + raw_cached := []datamodels.Server{} + d.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 data { + mapping_item_name := s.Ip + ":" + s.Port + _, ok := cached_servers[mapping_item_name] + if !ok { + fmt.Println(mapping_item_name + " not found!") + new_servers[mapping_item_name] = s + } else { + cached_servers[mapping_item_name].Ip = s.Ip + cached_servers[mapping_item_name].Port = s.Port + cached_servers[mapping_item_name].Name = s.Name + cached_servers[mapping_item_name].Players = s.Players + cached_servers[mapping_item_name].Maxplayers = s.Maxplayers + cached_servers[mapping_item_name].Ping = s.Ping + cached_servers[mapping_item_name].Map = s.Map + cached_servers[mapping_item_name].Gamemode = s.Gamemode + } + } + tx := d.Db.MustBegin() - for _, srv := range data { + fmt.Println("Adding new servers...") + for _, srv := range new_servers { tx.NamedExec("INSERT INTO servers (ip, port, name, ping, players, maxplayers, gamemode, map, version) VALUES (:ip, :port, :name, :ping, :players, :maxplayers, :gamemode, :map, :version)", 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, version=:version WHERE ip=:ip AND port=:port", &srv) + } + tx.Commit() fmt.Println("Done") } diff --git a/database/migrations.go b/database/migrations.go index bb0e0de..6ed4c58 100644 --- a/database/migrations.go +++ b/database/migrations.go @@ -55,6 +55,14 @@ func migrate_full(db *Database, version int) { three_to_four(db) version = 4 } + if version == 4 { + four_to_five(db) + version = 5 + } + if version == 5 { + five_to_six(db) + version = 6 + } } // Initial database structure. @@ -84,4 +92,19 @@ func two_to_three(db *Database) { func three_to_four(db *Database) { fmt.Println("Upgrading database from 3 to 4...") db.Db.MustExec("UPDATE urt_profiles SET version='4.3.0' WHERE version='4.3.000'") + db.Db.MustExec("UPDATE database SET version=4") +} + +// Server's passwords. +func four_to_five(db *Database) { + fmt.Println("Upgrading database from 4 to 5...") + db.Db.MustExec("ALTER TABLE servers ADD password VARCHAR(64) DEFAULT ''") + db.Db.MustExec("UPDATE database SET version=5") +} + +// Profile for server. +func five_to_six(db *Database) { + fmt.Println("Upgrading database from 5 to 6...") + db.Db.MustExec("ALTER TABLE servers ADD profile_to_use VARCHAR(128) DEFAULT ''") + db.Db.MustExec("UPDATE database SET version=6") } diff --git a/datamodels/server.go b/datamodels/server.go index f12f423..ed272fc 100644 --- a/datamodels/server.go +++ b/datamodels/server.go @@ -30,4 +30,8 @@ type Server struct { Version string `db:"version"` // Is server was favorited? Favorite string `db:"favorite"` + // Server's password. + Password string `db:"password"` + // Profile to use with server. + ProfileToUse string `db:"profile_to_use"` } diff --git a/requester/requester_object.go b/requester/requester_object.go index d844c43..98f6100 100644 --- a/requester/requester_object.go +++ b/requester/requester_object.go @@ -144,6 +144,12 @@ func (r *Requester) UpdateAllServers(done_chan chan map[string]*datamodels.Serve done_chan <- servers } +func (r *Requester) UpdateFavoriteServers(servers [][]string, done_chan chan map[string]*datamodels.Server, error_chan chan bool) { + fmt.Println("Updating favorites servers...") + updated_servers := r.updateServerGoroutineDispatcher(servers) + done_chan <- updated_servers +} + func (r *Requester) updateServerGoroutineDispatcher(data [][]string) map[string]*datamodels.Server { var wait sync.WaitGroup var lock = sync.RWMutex{} diff --git a/ui/favorite.go b/ui/favorite.go new file mode 100644 index 0000000..fed5c2c --- /dev/null +++ b/ui/favorite.go @@ -0,0 +1,223 @@ +// 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 ui + +import ( + // stdlib + "encoding/base64" + "errors" + "fmt" + "strings" + + // Local + "github.com/pztrn/urtrator/common" + "github.com/pztrn/urtrator/datamodels" + + // Other + "github.com/mattn/go-gtk/gdkpixbuf" + "github.com/mattn/go-gtk/gtk" +) + +type FavoriteDialog struct { + // Widgets. + // Dialog's window. + window *gtk.Window + // Main vertical box. + vbox *gtk.VBox + // Server name. + server_name *gtk.Entry + // Server address. + server_address *gtk.Entry + // Server password + server_password *gtk.Entry + // Profile. + profile *gtk.ComboBoxText + + // Flags. + // Is known server update performed? + update bool + + // Data. + // If known server is used - here server's datamodel is. + server *datamodels.Server +} + +func (f *FavoriteDialog) Close() {} + +func (f *FavoriteDialog) closeByCancel() { + f.window.Destroy() +} + +func (f *FavoriteDialog) fill() { + f.server_name.SetText(f.server.Name) + f.server_address.SetText(f.server.Ip + ":" + f.server.Port) + f.server_password.SetText(f.server.Password) + + // Profiles. + profiles := []datamodels.Profile{} + err := ctx.Database.Db.Select(&profiles, "SELECT * FROM urt_profiles") + if err != nil { + fmt.Println(err.Error()) + } + for p := range profiles { + if profiles[p].Version == f.server.Version { + f.profile.AppendText(profiles[p].Name) + } + } +} + +func (f *FavoriteDialog) Initialize() { + if f.update { + fmt.Println("Updating favorite server...") + } else { + fmt.Println("New favorite server") + } + + f.initializeWindow() +} + +func (f *FavoriteDialog) InitializeNew() { + f.update = false + f.Initialize() +} + +func (f *FavoriteDialog) InitializeUpdate(server *datamodels.Server) { + fmt.Println("Favorites updating...") + f.update = true + f.server = server + f.Initialize() + f.fill() +} + +func (f *FavoriteDialog) initializeWindow() { + f.window = gtk.NewWindow(gtk.WINDOW_TOPLEVEL) + if f.update { + f.window.SetTitle("URTrator - Updating favorite server") + } else { + f.window.SetTitle("URTrator - New favorite server") + } + f.window.Connect("destroy", f.Close) + f.window.SetPosition(gtk.WIN_POS_CENTER) + f.window.SetModal(true) + f.window.SetSizeRequest(400, 200) + f.window.SetResizable(false) + f.vbox = gtk.NewVBox(false, 0) + + // Load program icon from base64. + icon_bytes, _ := base64.StdEncoding.DecodeString(common.Logo) + icon_pixbuf := gdkpixbuf.NewLoader() + icon_pixbuf.Write(icon_bytes) + logo = icon_pixbuf.GetPixbuf() + f.window.SetIcon(logo) + + // Server name. + srv_name_hbox := gtk.NewHBox(false, 0) + f.vbox.PackStart(srv_name_hbox, false, true, 5) + srv_name_label := gtk.NewLabel("Server name:") + srv_name_hbox.PackStart(srv_name_label, false, true, 5) + srv_name_sep := gtk.NewHSeparator() + srv_name_hbox.PackStart(srv_name_sep, true, true, 5) + f.server_name = gtk.NewEntry() + srv_name_hbox.PackStart(f.server_name, true, true, 5) + + // Server address. + srv_addr_hbox := gtk.NewHBox(false, 0) + f.vbox.PackStart(srv_addr_hbox, false, true, 5) + srv_addr_label := gtk.NewLabel("Server address:") + srv_addr_hbox.PackStart(srv_addr_label, false, true, 5) + srv_addr_sep := gtk.NewHSeparator() + srv_addr_hbox.PackStart(srv_addr_sep, true, true, 5) + f.server_address = gtk.NewEntry() + srv_addr_hbox.PackStart(f.server_address, true, true, 5) + if f.update { + f.server_address.SetSensitive(false) + } + + // Server password. + srv_pass_hbox := gtk.NewHBox(false, 0) + f.vbox.PackStart(srv_pass_hbox, false, true, 5) + srv_pass_label := gtk.NewLabel("Password:") + srv_pass_hbox.PackStart(srv_pass_label, false, true, 5) + srv_pass_sep := gtk.NewHSeparator() + srv_pass_hbox.PackStart(srv_pass_sep, true, true, 5) + f.server_password = gtk.NewEntry() + srv_pass_hbox.PackStart(f.server_password, true, true, 5) + + // Profile to use. + profile_hbox := gtk.NewHBox(false, 0) + f.vbox.PackStart(profile_hbox, false, true, 5) + profile_label := gtk.NewLabel("Profile:") + profile_hbox.PackStart(profile_label, false, true, 5) + profile_sep := gtk.NewHSeparator() + profile_hbox.PackStart(profile_sep, true, true, 5) + f.profile = gtk.NewComboBoxText() + profile_hbox.PackStart(f.profile, false, true, 5) + + // Buttons hbox. + buttons_hbox := gtk.NewHBox(false, 0) + sep := gtk.NewHSeparator() + buttons_hbox.PackStart(sep, true, true, 5) + // OK-Cancel buttons. + cancel_button := gtk.NewButtonWithLabel("Cancel") + cancel_button.Clicked(f.closeByCancel) + buttons_hbox.PackStart(cancel_button, false, true, 5) + + ok_button := gtk.NewButtonWithLabel("OK") + ok_button.Clicked(f.saveFavorite) + buttons_hbox.PackStart(ok_button, false, true, 5) + + f.vbox.PackStart(buttons_hbox, false, true, 5) + + f.window.Add(f.vbox) + f.window.ShowAll() +} + +func (f *FavoriteDialog) saveFavorite() error { + if len(f.server_address.GetText()) == 0 { + mbox_string := "Server address is empty.\n\nServers without address cannot be added." + m := gtk.NewMessageDialog(f.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, mbox_string) + m.Response(func() { + m.Destroy() + }) + m.Run() + return errors.New("No server address specified") + } + + var port string = "" + if strings.Contains(f.server_address.GetText(), ":") { + port = strings.Split(f.server_address.GetText(), ":")[1] + } else { + port = "27960" + } + + fmt.Println("Saving favorite server...") + + server := datamodels.Server{} + server.Ip = strings.Split(f.server_address.GetText(), ":")[0] + server.Port = port + server.Name = f.server_name.GetText() + server.Password = f.server_password.GetText() + server.ProfileToUse = f.profile.GetActiveText() + server.Favorite = "1" + + if f.update { + q := "UPDATE servers SET name=:name, ip=:ip, port=:port, password=:password, favorite=:favorite, profile_to_use=:profile_to_use WHERE ip='" + f.server.Ip + "' AND port='" + f.server.Port + "'" + fmt.Println("Query: " + q) + ctx.Database.Db.NamedExec(q, &server) + } else { + q := "INSERT INTO servers (name, ip, port, password, favorite, profile_to_use) VALUES (:name, :ip, :port, :password, \"1\", :profile_to_use)" + fmt.Println(q) + ctx.Database.Db.NamedExec(q, &server) + } + + ctx.Eventer.LaunchEvent("loadFavoriteServers") + + return nil +} diff --git a/ui/mainwindow.go b/ui/mainwindow.go index b26e8b4..538d5fe 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -15,6 +15,7 @@ import ( "errors" "fmt" "strconv" + "strings" // Local "github.com/pztrn/urtrator/common" @@ -61,6 +62,10 @@ type MainWindow struct { fav_servers_hide_offline *gtk.CheckButton // Game launch button. launch_button *gtk.Button + // Quick connect: server address + qc_server_address *gtk.Entry + // Quick connect: password + qc_password *gtk.Entry // Storages. // All servers store. @@ -72,6 +77,8 @@ type MainWindow struct { // Dialogs. options_dialog *OptionsDialog + // Favorite server editing. + favorite_dialog *FavoriteDialog // Other // Old profiles count. @@ -80,12 +87,125 @@ type MainWindow struct { func (m *MainWindow) addToFavorites() { fmt.Println("Adding server to favorites...") + + current_tab := m.tab_widget.GetTabLabelText(m.tab_widget.GetNthPage(m.tab_widget.GetCurrentPage())) + + // Getting server's address from list. + sel := m.all_servers.GetSelection() + model := m.all_servers.GetModel() + if strings.Contains(current_tab, "Favorites") { + // Getting server's address from list. + sel = m.fav_servers.GetSelection() + model = m.fav_servers.GetModel() + } + iter := new(gtk.TreeIter) + _ = sel.GetSelected(iter) + + // Getting server address. + var srv_addr string + srv_addr_gval := glib.ValueFromNative(srv_addr) + model.GetValue(iter, 7, srv_addr_gval) + server_address := srv_addr_gval.GetString() + + // Getting server from database. + m.favorite_dialog = &FavoriteDialog{} + if len(server_address) > 0 { + servers := []datamodels.Server{} + address := strings.Split(server_address, ":")[0] + port := strings.Split(server_address, ":")[1] + err1 := ctx.Database.Db.Select(&servers, ctx.Database.Db.Rebind("SELECT * FROM servers WHERE ip=? AND port=?"), address, port) + if err1 != nil { + fmt.Println(err1.Error()) + } + m.favorite_dialog.InitializeUpdate(&servers[0]) + } else { + m.favorite_dialog.InitializeNew() + } } func (m *MainWindow) Close() { ctx.Close() } +func (m *MainWindow) deleteFromFavorites() { + fmt.Println("Removing server from favorites...") + current_tab := m.tab_widget.GetTabLabelText(m.tab_widget.GetNthPage(m.tab_widget.GetCurrentPage())) + + // Assuming that deletion was called from "Servers" tab by default. + sel := m.all_servers.GetSelection() + model := m.all_servers.GetModel() + if strings.Contains(current_tab, "Favorites") { + // Getting server's address from list. + sel = m.fav_servers.GetSelection() + model = m.fav_servers.GetModel() + } + + iter := new(gtk.TreeIter) + _ = sel.GetSelected(iter) + + // Getting server address. + var srv_addr string + srv_addr_gval := glib.ValueFromNative(srv_addr) + model.GetValue(iter, 7, srv_addr_gval) + server_address := srv_addr_gval.GetString() + + var not_favorited bool = false + if len(server_address) > 0 { + address := strings.Split(server_address, ":")[0] + port := strings.Split(server_address, ":")[1] + srv := []datamodels.Server{} + err := ctx.Database.Db.Select(&srv, ctx.Database.Db.Rebind("SELECT * FROM servers WHERE ip=? AND port=?"), address, port) + if err != nil { + fmt.Println(err.Error()) + } + if srv[0].Favorite == "1" { + ctx.Database.Db.MustExec(ctx.Database.Db.Rebind("UPDATE servers SET favorite='0' WHERE ip=? AND port=?"), address, port) + ctx.Eventer.LaunchEvent("loadFavoriteServers") + } else { + not_favorited = true + } + } else { + not_favorited = true + } + + if not_favorited { + mbox_string := "Cannot delete server from favorites.\n\nServer isn't favorited." + d := gtk.NewMessageDialog(m.window, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, mbox_string) + d.Response(func() { + d.Destroy() + }) + d.Run() + } +} + +func (m *MainWindow) dropDatabasesData() { + fmt.Println("Dropping database data...") + var will_continue bool = false + mbox_string := "You are about to drop whole database data.\n\nAfter clicking \"YES\" ALL data in database (servers, profiles, settings, etc.)\nwill be lost FOREVER. Are you sure?" + d := gtk.NewMessageDialog(m.window, gtk.DIALOG_MODAL, gtk.MESSAGE_WARNING, gtk.BUTTONS_YES_NO, mbox_string) + d.Connect("response", func(resp *glib.CallbackContext) { + if resp.Args(0) == 4294967287 { + will_continue = false + } else { + will_continue = true + } + }) + d.Response(func() { + d.Destroy() + }) + d.Run() + + if will_continue { + ctx.Database.Db.MustExec("DELETE FROM servers") + ctx.Database.Db.MustExec("DELETE FROM settings") + ctx.Database.Db.MustExec("DELETE FROM urt_profiles") + + ctx.Eventer.LaunchEvent("loadProfiles") + ctx.Eventer.LaunchEvent("loadAllServers") + ctx.Eventer.LaunchEvent("loadFavoriteServers") + } +} + func (m *MainWindow) hideOfflineAllServers() { fmt.Println("(Un)Hiding offline servers in 'Servers' tab...") ctx.Eventer.LaunchEvent("loadAllServers") @@ -93,6 +213,7 @@ func (m *MainWindow) hideOfflineAllServers() { func (m *MainWindow) hideOfflineFavoriteServers() { fmt.Println("(Un)Hiding offline servers in 'Favorite' tab...") + ctx.Eventer.LaunchEvent("loadFavoriteServers") } func (m *MainWindow) Initialize() { @@ -137,7 +258,7 @@ func (m *MainWindow) Initialize() { // Temporary hack. var w, h int = 0, 0 m.window.GetSize(&w, &h) - m.hpane.SetPosition(w) + m.hpane.SetPosition(w - 150) // Game profiles and launch button. profile_and_launch_hbox := gtk.NewHBox(false, 0) @@ -180,6 +301,7 @@ func (m *MainWindow) Initialize() { // Launch events. ctx.Eventer.LaunchEvent("loadProfiles") ctx.Eventer.LaunchEvent("loadAllServers") + ctx.Eventer.LaunchEvent("loadFavoriteServers") gtk.Main() } @@ -199,6 +321,10 @@ func (m *MainWindow) InitializeMainMenu() { file_menu.Append(options_menu_item) options_menu_item.Connect("activate", m.options_dialog.ShowOptionsDialog) + // Separator. + file_menu_sep1 := gtk.NewSeparatorMenuItem() + file_menu.Append(file_menu_sep1) + // Exit. exit_menu_item := gtk.NewMenuItemWithMnemonic("E_xit") file_menu.Append(exit_menu_item) @@ -214,11 +340,46 @@ func (m *MainWindow) InitializeMainMenu() { about_app_item := gtk.NewMenuItemWithMnemonic("About _URTrator...") about_menu.Append(about_app_item) about_app_item.Connect("activate", ShowAboutDialog) + + // Separator. + about_menu_sep1 := gtk.NewSeparatorMenuItem() + about_menu.Append(about_menu_sep1) + + // Drop databases thing. + about_menu_drop_database_data_item := gtk.NewMenuItemWithMnemonic("Drop database data...") + about_menu.Append(about_menu_drop_database_data_item) + about_menu_drop_database_data_item.Connect("activate", m.dropDatabasesData) } func (m *MainWindow) initializeSidebar() { sidebar_vbox := gtk.NewVBox(false, 0) + // Quick connect frame. + quick_connect_frame := gtk.NewFrame("Quick connect") + sidebar_vbox.PackStart(quick_connect_frame, true, true, 5) + qc_vbox := gtk.NewVBox(false, 0) + quick_connect_frame.Add(qc_vbox) + + // Server address. + srv_tooltip := "Server address we will connect to" + srv_label := gtk.NewLabel("Server address:") + srv_label.SetTooltipText(srv_tooltip) + qc_vbox.PackStart(srv_label, false, true, 5) + + m.qc_server_address = gtk.NewEntry() + m.qc_server_address.SetTooltipText(srv_tooltip) + qc_vbox.PackStart(m.qc_server_address, false, true, 5) + + // Password. + pass_tooltip := "Password we will use for server" + pass_label := gtk.NewLabel("Password:") + pass_label.SetTooltipText(pass_tooltip) + qc_vbox.PackStart(pass_label, false, true, 5) + + m.qc_password = gtk.NewEntry() + m.qc_password.SetTooltipText(pass_tooltip) + qc_vbox.PackStart(m.qc_password, false, true, 5) + m.hpane.Add2(sidebar_vbox) } @@ -331,13 +492,34 @@ func (m *MainWindow) InitializeTabs() { m.tab_widget.AppendPage(tab_fav_srv_hbox, gtk.NewLabel("Favorites")) m.fav_servers.SetModel(m.fav_servers_store) m.fav_servers.AppendColumn(gtk.NewTreeViewColumnWithAttributes("Status", gtk.NewCellRendererPixbuf(), "pixbuf", 0)) - m.fav_servers.AppendColumn(gtk.NewTreeViewColumnWithAttributes("Name", gtk.NewCellRendererText(), "text", 1)) - m.fav_servers.AppendColumn(gtk.NewTreeViewColumnWithAttributes("Mode", gtk.NewCellRendererText(), "text", 2)) - m.fav_servers.AppendColumn(gtk.NewTreeViewColumnWithAttributes("Map", gtk.NewCellRendererText(), "text", 3)) - m.fav_servers.AppendColumn(gtk.NewTreeViewColumnWithAttributes("Players", gtk.NewCellRendererText(), "text", 4)) - m.fav_servers.AppendColumn(gtk.NewTreeViewColumnWithAttributes("Ping", gtk.NewCellRendererText(), "text", 5)) - m.fav_servers.AppendColumn(gtk.NewTreeViewColumnWithAttributes("Version", gtk.NewCellRendererText(), "text", 6)) - m.fav_servers.AppendColumn(gtk.NewTreeViewColumnWithAttributes("IP", gtk.NewCellRendererText(), "text", 7)) + + fav_name_column := gtk.NewTreeViewColumnWithAttributes("Name", gtk.NewCellRendererText(), "text", 1) + fav_name_column.SetSortColumnId(1) + m.fav_servers.AppendColumn(fav_name_column) + + fav_mode_column := gtk.NewTreeViewColumnWithAttributes("Mode", gtk.NewCellRendererText(), "text", 2) + fav_mode_column.SetSortColumnId(2) + m.fav_servers.AppendColumn(fav_mode_column) + + fav_map_column := gtk.NewTreeViewColumnWithAttributes("Map", gtk.NewCellRendererText(), "text", 3) + fav_map_column.SetSortColumnId(3) + m.fav_servers.AppendColumn(fav_map_column) + + fav_players_column := gtk.NewTreeViewColumnWithAttributes("Players", gtk.NewCellRendererText(), "text", 4) + fav_players_column.SetSortColumnId(4) + m.fav_servers.AppendColumn(fav_players_column) + + fav_ping_column := gtk.NewTreeViewColumnWithAttributes("Ping", gtk.NewCellRendererText(), "text", 5) + fav_ping_column.SetSortColumnId(5) + m.fav_servers.AppendColumn(fav_ping_column) + + fav_version_column := gtk.NewTreeViewColumnWithAttributes("Version", gtk.NewCellRendererText(), "text", 6) + fav_version_column.SetSortColumnId(6) + m.fav_servers.AppendColumn(fav_version_column) + + fav_ip_column := gtk.NewTreeViewColumnWithAttributes("IP", gtk.NewCellRendererText(), "text", 7) + fav_ip_column.SetSortColumnId(7) + m.fav_servers.AppendColumn(fav_ip_column) // VBox for some servers list controllers. tab_fav_srv_ctl_vbox := gtk.NewVBox(false, 0) @@ -356,7 +538,8 @@ func (m *MainWindow) InitializeTabs() { // Add tab_widget widget to window. m.hpane.Add1(m.tab_widget) - ctx.Eventer.AddEventHandler("loadAllServers", m.loadServers) + ctx.Eventer.AddEventHandler("loadAllServers", m.loadAllServers) + ctx.Eventer.AddEventHandler("loadFavoriteServers", m.loadFavoriteServers) } func (m *MainWindow) InitializeToolbar() { @@ -380,17 +563,26 @@ func (m *MainWindow) InitializeToolbar() { fav_button.SetTooltipText("Add selected server to favorites") fav_button.OnClicked(m.addToFavorites) m.toolbar.Insert(fav_button, 2) + + // Remove server from favorites button. + fav_delete_button := gtk.NewToolButtonFromStock(gtk.STOCK_REMOVE) + fav_delete_button.SetLabel("Remove from favorites") + fav_delete_button.SetTooltipText("Remove selected server from favorites") + fav_delete_button.OnClicked(m.deleteFromFavorites) + m.toolbar.Insert(fav_delete_button, 3) } func (m *MainWindow) launchGame() error { fmt.Println("Launching Urban Terror...") - //var launch_params string = "" - // Getting server's name from list. - // ToDo: detect on what tab we are and use approriate list. + current_tab := m.tab_widget.GetTabLabelText(m.tab_widget.GetNthPage(m.tab_widget.GetCurrentPage())) sel := m.all_servers.GetSelection() model := m.all_servers.GetModel() + if strings.Contains(current_tab, "Favorites") { + sel = m.fav_servers.GetSelection() + model = m.fav_servers.GetModel() + } iter := new(gtk.TreeIter) _ = sel.GetSelected(iter) @@ -412,7 +604,6 @@ func (m *MainWindow) launchGame() error { model.GetValue(iter, 6, srv_game_ver_gval) srv_game_ver := srv_game_ver_gval.GetString() - // Check for proper server name. If length == 0: server is offline, // we should show notification to user. if len(server_name) == 0 { @@ -437,19 +628,33 @@ func (m *MainWindow) launchGame() error { // Getting selected profile's name. profile_name := m.profiles.GetActiveText() - // Checking profile name length. If 0 - then stop executing :) - if len(profile_name) == 0 { - mbox_string := "Invalid game profile selected.\n\nPlease, select profile and retry." - m := gtk.NewMessageDialog(m.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, mbox_string) - m.Response(func() { - m.Destroy() - }) - m.Run() - return errors.New("User didn't select valid profile.") + if strings.Contains(current_tab, "Servers") { + // Checking profile name length. If 0 - then stop executing :) + // This check only relevant to "Servers" tab, favorite servers + // have profiles defined (see next). + if len(profile_name) == 0 { + mbox_string := "Invalid game profile selected.\n\nPlease, select profile and retry." + m := gtk.NewMessageDialog(m.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, mbox_string) + m.Response(func() { + m.Destroy() + }) + m.Run() + return errors.New("User didn't select valid profile.") + } + } else if strings.Contains(current_tab, "Favorites") { + // For favorite servers profile specified in favorite server + // information have higher priority, so we just override it :) + server := []datamodels.Server{} + // All favorites servers should contain IP and Port :) + ip := strings.Split(srv_address, ":")[0] + port := strings.Split(srv_address, ":")[1] + err := ctx.Database.Db.Select(&server, ctx.Database.Db.Rebind("SELECT * FROM servers WHERE ip=? AND port=?"), ip, port) + if err != nil { + fmt.Println(err.Error()) + } + profile_name = server[0].ProfileToUse } - fmt.Println("Connecting to " + server_name + " (" + srv_address + ") using profile " + profile_name + "...") - // Getting profile data from database. // ToDo: cache profiles data in runtime. profile := []datamodels.Profile{} @@ -478,28 +683,7 @@ func (m *MainWindow) launchGame() error { return nil } -func (m *MainWindow) loadProfiles() { - fmt.Println("Loading profiles into combobox on MainWindow") - for i := 0; i < m.old_profiles_count; i++ { - // ComboBox indexes are shifting on element deletion, so we should - // detele very first element every time. - m.profiles.Remove(0) - } - - profiles := []datamodels.Profile{} - err := ctx.Database.Db.Select(&profiles, "SELECT * FROM urt_profiles") - if err != nil { - fmt.Println(err.Error()) - } - for p := range profiles { - m.profiles.AppendText(profiles[p].Name) - } - - m.old_profiles_count = len(profiles) - fmt.Println("Added " + strconv.Itoa(m.old_profiles_count) + " profiles") -} - -func (m *MainWindow) loadServers() { +func (m *MainWindow) loadAllServers() { fmt.Println("Loading servers lists into widgets...") servers := []datamodels.Server{} err := ctx.Database.Db.Select(&servers, "SELECT * FROM servers") @@ -529,28 +713,103 @@ func (m *MainWindow) loadServers() { } } +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" { + continue + } + if m.fav_servers_hide_offline.GetActive() && srv.Name == "" && srv.Players == "" { + continue + } + var iter gtk.TreeIter + m.fav_servers_store.Append(&iter) + if srv.Name == "" && srv.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) + } + 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) + } +} + +func (m *MainWindow) loadProfiles() { + fmt.Println("Loading profiles into combobox on MainWindow") + for i := 0; i < m.old_profiles_count; i++ { + // ComboBox indexes are shifting on element deletion, so we should + // detele very first element every time. + m.profiles.Remove(0) + } + + profiles := []datamodels.Profile{} + err := ctx.Database.Db.Select(&profiles, "SELECT * FROM urt_profiles") + if err != nil { + fmt.Println(err.Error()) + } + for p := range profiles { + m.profiles.AppendText(profiles[p].Name) + } + + m.old_profiles_count = len(profiles) + fmt.Println("Added " + strconv.Itoa(m.old_profiles_count) + " profiles") +} + func (m *MainWindow) unlockInterface() { m.launch_button.SetSensitive(true) 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) +} + 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 current_tab == "Servers" { + if strings.Contains(current_tab, "Servers") { go ctx.Requester.UpdateAllServers(done_chan, error_chan) - } else if current_tab == "Favorites" { - fmt.Println("Favorites update stub") + } else if strings.Contains(current_tab, "Favorites") { + m.updateFavorites(done_chan, error_chan) } select { case data := <- done_chan: fmt.Println("Information about servers successfully gathered") ctx.Database.UpdateServers(data) - ctx.Eventer.LaunchEvent("loadAllServers") + if current_tab == "Servers" { + ctx.Eventer.LaunchEvent("loadAllServers") + } else { + ctx.Eventer.LaunchEvent("loadFavoriteServers") + } case <- error_chan: fmt.Println("Error occured") }