From 4020d52c03dc7d216ad846c8cf240026c17c8544 Mon Sep 17 00:00:00 2001 From: "Stanislav N. aka pztrn" Date: Tue, 13 Dec 2016 08:01:48 +0500 Subject: [PATCH] Timer for tasks, servers autoupdating and other. Added Timer - great package which will make periodic tasks to work. Timer exposes "taskDone" event, which can be triggered when task ends it's execution. Added first periodic task - servers information updating. By default it will execute every 10 minutes. Configurable thru options. Added new options pane: Servers updating. It controls all aspects of servers updating thing. Master server address, servers autoupdating checkbox and autoupdate timeout values are exposed for now. We're now on on 0.2.0-devel :). Fixes #29. --- common/const.go | 2 +- context/context_object.go | 11 ++- requester/exported.go | 8 ++- requester/pooler.go | 3 + requester/requester_object.go | 49 +++++++++---- timer/exported.go | 32 +++++++++ timer/timer.go | 125 ++++++++++++++++++++++++++++++++++ timer/timertask.go | 34 +++++++++ ui/gtk2/mainwindow.go | 8 ++- ui/gtk2/mainwindow_init.go | 35 ++++++++++ ui/gtk2/options.go | 102 ++++++++++++++++++++++++++- 11 files changed, 392 insertions(+), 17 deletions(-) create mode 100644 timer/exported.go create mode 100644 timer/timer.go create mode 100644 timer/timertask.go diff --git a/common/const.go b/common/const.go index 53c4bf9..a3c9dc4 100644 --- a/common/const.go +++ b/common/const.go @@ -10,7 +10,7 @@ package common const ( - URTRATOR_VERSION = "0.1.1" + URTRATOR_VERSION = "0.2.0-devel" ) // Self-named. diff --git a/context/context_object.go b/context/context_object.go index 22789be..e49a4ee 100644 --- a/context/context_object.go +++ b/context/context_object.go @@ -23,6 +23,7 @@ import ( "github.com/pztrn/urtrator/eventer" "github.com/pztrn/urtrator/launcher" "github.com/pztrn/urtrator/requester" + "github.com/pztrn/urtrator/timer" // Github "github.com/mattn/go-gtk/gtk" @@ -45,6 +46,8 @@ type Context struct { Launcher *launcher.Launcher // Requester, which requests server's information. Requester *requester.Requester + // Timer. + Timer *timer.Timer } func (ctx *Context) Close() error { @@ -100,10 +103,15 @@ func (ctx *Context) initializeLauncher() { } func (ctx *Context) initializeRequester() { - ctx.Requester = requester.New(ctx.Cache, ctx.Eventer) + ctx.Requester = requester.New(ctx.Cache, ctx.Eventer, ctx.Cfg, ctx.Timer) ctx.Requester.Initialize() } +func (ctx *Context) initializeTimer() { + ctx.Timer = timer.New(ctx.Eventer, ctx.Cfg) + ctx.Timer.Initialize() +} + func (ctx *Context) Initialize() { fmt.Println("Initializing application context...") ctx.initializeColorizer() @@ -112,5 +120,6 @@ func (ctx *Context) Initialize() { ctx.initializeEventer() ctx.initializeCache() ctx.initializeLauncher() + ctx.initializeTimer() ctx.initializeRequester() } diff --git a/requester/exported.go b/requester/exported.go index 67c8bdf..f141dff 100644 --- a/requester/exported.go +++ b/requester/exported.go @@ -15,17 +15,23 @@ import ( // local "github.com/pztrn/urtrator/cache" + "github.com/pztrn/urtrator/configuration" "github.com/pztrn/urtrator/eventer" + "github.com/pztrn/urtrator/timer" ) var ( Cache *cache.Cache + Cfg *configuration.Config Eventer *eventer.Eventer + Timer *timer.Timer ) -func New(c *cache.Cache, e *eventer.Eventer) *Requester { +func New(c *cache.Cache, e *eventer.Eventer, cc *configuration.Config, t *timer.Timer) *Requester { Cache = c + Cfg = cc Eventer = e + Timer = t fmt.Println("Creating Requester object...") r := Requester{} return &r diff --git a/requester/pooler.go b/requester/pooler.go index c9b5668..b35e2ab 100644 --- a/requester/pooler.go +++ b/requester/pooler.go @@ -68,9 +68,12 @@ func (p *Pooler) PingServers(servers_type string) { 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 } } diff --git a/requester/requester_object.go b/requester/requester_object.go index 8947ec1..0855d63 100644 --- a/requester/requester_object.go +++ b/requester/requester_object.go @@ -16,16 +16,13 @@ import ( "fmt" "net" "strconv" + "strings" "time" ) type Requester struct { // Pooler. Pooler *Pooler - // Master server address - master_server string - // Master server port - master_server_port string // Packet prefix. pp string // Showstopper for delimiting IP addresses. @@ -37,8 +34,6 @@ type Requester struct { // Requester's initialization. func (r *Requester) Initialize() { fmt.Println("Initializing Requester...") - r.master_server = "master.urbanterror.info" - r.master_server_port = "27900" r.pp = "\377\377\377\377" r.ip_delimiter = 92 r.Pooler = &Pooler{} @@ -48,8 +43,20 @@ func (r *Requester) Initialize() { // Gets all available servers from master server. // This isn't in pooler, because it have no need to be pooled. func (r *Requester) getServers() error { + // Get master server address and port from configuration. + var master_server string = "" + var master_server_port string = "" + master_server_raw, ok := Cfg.Cfg["/servers_updating/master_server"] + if ok { + master_server = strings.Split(master_server_raw, ":")[0] + master_server_port = strings.Split(master_server_raw, ":")[1] + } else { + master_server = "master.urbanterror.info" + master_server_port = "27900" + } + fmt.Println("Using master server address: " + master_server + ":" + master_server_port) // IP addresses we will compose to return. - conn, err1 := net.Dial("udp", r.master_server + ":" + r.master_server_port) + conn, err1 := net.Dial("udp", master_server + ":" + master_server_port) if err1 != nil { fmt.Println("Error dialing to master server!") Eventer.LaunchEvent("setToolbarLabelText", map[string]string{"text": "Failed to connect to master server!"}) @@ -80,11 +87,20 @@ func (r *Requester) getServers() error { fmt.Println("Receiving servers list...") for { _, err := conn.Read(received_buf) - if err != nil { - break - } raw_received = append(raw_received, received_buf...) fmt.Println("Received " + strconv.Itoa(len(raw_received)) + " bytes") + + if err != nil { + // A bit hacky - if we have received data length lower or + // equal to 4k - we have an error. Looks like conn.Read() + // reads by 4k. + if len(raw_received) < 4097 { + fmt.Println("Error dialing to master server!") + Eventer.LaunchEvent("setToolbarLabelText", map[string]string{"text": "Failed to connect to master server!"}) + return errors.New("Failed to connect to master server!") + } + break + } } // Obtaining list of slices. @@ -117,8 +133,10 @@ func (r *Requester) getServers() error { if !ok { // Create cached server. Cache.CreateServer(addr) + Cache.ServersMutex.Lock() Cache.Servers[addr].Server.Ip = ip Cache.Servers[addr].Server.Port = port + Cache.ServersMutex.Unlock() } } @@ -127,10 +145,17 @@ func (r *Requester) getServers() error { // Updates information about all available servers from master server and // parses it to usable format. -func (r *Requester) UpdateAllServers() { +func (r *Requester) UpdateAllServers(task bool) { fmt.Println("Starting all servers updating procedure...") - r.getServers() + err := r.getServers() + if err != nil { + return + } r.Pooler.UpdateServers("all") + + if task { + Eventer.LaunchEvent("taskDone", map[string]string{"task_name": "Server's autoupdating"}) + } } func (r *Requester) UpdateFavoriteServers() { diff --git a/timer/exported.go b/timer/exported.go new file mode 100644 index 0000000..11fb5e5 --- /dev/null +++ b/timer/exported.go @@ -0,0 +1,32 @@ +// 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 timer + +import ( + // stdlib + "fmt" + + // local + "github.com/pztrn/urtrator/configuration" + "github.com/pztrn/urtrator/eventer" +) + +var ( + Cfg *configuration.Config + Eventer *eventer.Eventer +) + +func New(e *eventer.Eventer, cc *configuration.Config) *Timer { + Cfg = cc + Eventer = e + fmt.Println("Creating Timer object...") + t := Timer{} + return &t +} diff --git a/timer/timer.go b/timer/timer.go new file mode 100644 index 0000000..1cd3277 --- /dev/null +++ b/timer/timer.go @@ -0,0 +1,125 @@ +// 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 timer + +import ( + // stdlib + "errors" + "fmt" + "strconv" + "sync" + "time" +) + +type Timer struct { + // Tasks. + tasks map[string]*TimerTask + // Tasks map mutex. + tasksMutex sync.Mutex +} + +func (t *Timer) AddTask(task *TimerTask) error { + fmt.Println("Adding task '" + task.Name + "'...") + + _, ok := t.tasks[task.Name] + if ok { + error_text := "Task '" + task.Name + "' already exist! Ignoring..." + fmt.Println(error_text) + return errors.New(error_text) + } + + task.InProgress = false + + curtime := time.Now() + nextlaunch := curtime.Add(time.Duration(task.Timeout) * time.Second) + task.NextLaunch = nextlaunch + + t.tasksMutex.Lock() + t.tasks[task.Name] = task + t.tasksMutex.Unlock() + + fmt.Println("Added task '" + task.Name + "' with " + strconv.Itoa(task.Timeout) + " seconds timeout") + return nil +} + +func (t *Timer) executeTasks() { + t.tasksMutex.Lock() + for task_name, task := range t.tasks { + // Check if task should be run. + curtime := time.Now() + diff := curtime.Sub(task.NextLaunch) + //fmt.Println(diff) + if diff > 0 { + fmt.Println("Checking task '" + task_name + "'...") + // Check if task is already running. + if task.InProgress { + fmt.Println("Already executing, skipping...") + continue + } + + fmt.Println("Launching task '" + task_name + "'...") + task.InProgress = true + Eventer.LaunchEvent(task.Callee, map[string]string{}) + + curtime = time.Now() + nextlaunch := curtime.Add(time.Duration(task.Timeout) * time.Second) + task.NextLaunch = nextlaunch + } + } + t.tasksMutex.Unlock() +} + +func (t *Timer) GetTaskStatus(task_name string) bool { + _, ok := t.tasks[task_name] + if !ok { + return false + } + + return t.tasks[task_name].InProgress +} + +func (t *Timer) Initialize() { + fmt.Println("Initializing timer...") + + t.initializeStorage() + + ticker := time.NewTicker(time.Second * 1) + go func() { + for _ = range ticker.C { + go t.executeTasks() + } + }() + + Eventer.AddEventHandler("taskDone", t.SetTaskNotInProgress) +} + +func (t *Timer) initializeStorage() { + t.tasks = make(map[string]*TimerTask) +} + +func (t *Timer) RemoveTask(task_name string) { + _, ok := t.tasks[task_name] + if !ok { + return + } + + t.tasksMutex.Lock() + delete(t.tasks, task_name) + t.tasksMutex.Unlock() +} + +func (t *Timer) SetTaskNotInProgress(data map[string]string) { + _, ok := t.tasks[data["task_name"]] + if !ok { + return + } + + t.tasks[data["task_name"]].InProgress = false +} diff --git a/timer/timertask.go b/timer/timertask.go new file mode 100644 index 0000000..f4a652f --- /dev/null +++ b/timer/timertask.go @@ -0,0 +1,34 @@ +// 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 timer + +import ( + // stdlib + "time" +) + +type TimerTask struct { + // Task name. + Name string + // Task timeout, in seconds. + Timeout int + // What we should call? + // This should be an event name. + Callee string + + // Internal variables, used by Timer. + // These variables can be defined, but they will be most likely + // overrided after first task launch. + // Next task launch time. + NextLaunch time.Time + // Is task currently executed? + // Kinda alternative to mutex. + InProgress bool +} diff --git a/ui/gtk2/mainwindow.go b/ui/gtk2/mainwindow.go index 37fc049..357491e 100644 --- a/ui/gtk2/mainwindow.go +++ b/ui/gtk2/mainwindow.go @@ -808,8 +808,14 @@ func (m *MainWindow) UpdateServers() { fmt.Println("Updating servers on tab '" + current_tab + "'...") if strings.Contains(current_tab, "Servers") { - go ctx.Requester.UpdateAllServers() + go ctx.Requester.UpdateAllServers(false) } else if strings.Contains(current_tab, "Favorites") { go ctx.Requester.UpdateFavoriteServers() } } + +func (m *MainWindow) UpdateServersEventHandler(data map[string]string) { + ctx.Eventer.LaunchEvent("setToolbarLabelText", map[string]string{"text": "Updating servers..."}) + + go ctx.Requester.UpdateAllServers(true) +} diff --git a/ui/gtk2/mainwindow_init.go b/ui/gtk2/mainwindow_init.go index 93f5d08..5c1ad8a 100644 --- a/ui/gtk2/mainwindow_init.go +++ b/ui/gtk2/mainwindow_init.go @@ -10,6 +10,7 @@ import ( // local "github.com/pztrn/urtrator/common" + "github.com/pztrn/urtrator/timer" // Other "github.com/mattn/go-gtk/gdkpixbuf" @@ -169,6 +170,7 @@ func (m *MainWindow) Initialize() { ctx.Eventer.LaunchEvent("loadServersIntoCache", map[string]string{}) ctx.Eventer.LaunchEvent("loadAllServers", map[string]string{}) ctx.Eventer.LaunchEvent("loadFavoriteServers", map[string]string{}) + ctx.Eventer.LaunchEvent("initializeTasksForMainWindow", map[string]string{}) ctx.Eventer.LaunchEvent("setToolbarLabelText", map[string]string{"text": "URTrator is ready."}) // Set flag that shows to other parts that we're initialized. @@ -180,12 +182,14 @@ func (m *MainWindow) Initialize() { // Events initialization. func (m *MainWindow) initializeEvents() { fmt.Println("Initializing events...") + ctx.Eventer.AddEventHandler("initializeTasksForMainWindow", m.initializeTasks) ctx.Eventer.AddEventHandler("loadAllServers", m.loadAllServers) ctx.Eventer.AddEventHandler("loadFavoriteServers", m.loadFavoriteServers) ctx.Eventer.AddEventHandler("loadProfilesIntoMainWindow", m.loadProfiles) ctx.Eventer.AddEventHandler("serversUpdateCompleted", m.serversUpdateCompleted) ctx.Eventer.AddEventHandler("setQuickConnectDetails", m.setQuickConnectDetails) ctx.Eventer.AddEventHandler("setToolbarLabelText", m.setToolbarLabelText) + ctx.Eventer.AddEventHandler("updateAllServers", m.UpdateServersEventHandler) } // Main menu initialization. @@ -706,6 +710,37 @@ func (m *MainWindow) InitializeTabs() { m.hpane.Add1(m.tab_widget) } +// Tasks. +func (m *MainWindow) initializeTasks(data map[string]string) { + // Get task status, if it already running. + task_status := ctx.Timer.GetTaskStatus("Server's autoupdating") + // Remove tasks if they exist. + ctx.Timer.RemoveTask("Server's autoupdating") + + // Add servers autoupdate task. + if ctx.Cfg.Cfg["/servers_updating/servers_autoupdate"] == "1" { + task := timer.TimerTask{ + Name: "Server's autoupdating", + Callee: "updateAllServers", + InProgress: task_status, + } + + timeout, ok := ctx.Cfg.Cfg["/servers_updating/servers_autoupdate_timeout"] + if ok { + timeout_int, err := strconv.Atoi(timeout) + if err != nil { + task.Timeout = 10 * 60 + } else { + task.Timeout = timeout_int * 60 + } + } else { + task.Timeout = 10 * 60 + } + + ctx.Timer.AddTask(&task) + } +} + // Toolbar initialization. func (m *MainWindow) InitializeToolbar() { m.toolbar = gtk.NewToolbar() diff --git a/ui/gtk2/options.go b/ui/gtk2/options.go index b9c8003..411b4cb 100644 --- a/ui/gtk2/options.go +++ b/ui/gtk2/options.go @@ -39,6 +39,13 @@ type OptionsDialog struct { // Urban Terror tab. // Profiles list. profiles_list *gtk.TreeView + // Servers updating tab. + // Master server address. + master_server_addr *gtk.Entry + // Servers autoupdate. + servers_autoupdate *gtk.CheckButton + // Timeout for servers autoupdating. + servers_autoupdate_timeout *gtk.Entry // Data stores. // Urban Terror profiles list. @@ -131,6 +138,29 @@ func (o *OptionsDialog) fill() { if ctx.Cfg.Cfg["/general/urtrator_autoupdate"] == "1" { o.autoupdate.SetActive(true) } + + // Servers updating tab. + master_server_addr, ok := ctx.Cfg.Cfg["/servers_updating/master_server"] + if !ok { + o.master_server_addr.SetText("master.urbanterror.info:27900") + } else { + o.master_server_addr.SetText(master_server_addr) + } + + servers_autoupdate, ok1 := ctx.Cfg.Cfg["/servers_updating/servers_autoupdate"] + if ok1 { + if servers_autoupdate == "1" { + o.servers_autoupdate.SetActive(true) + } + } + + servers_update_timeout, ok2 := ctx.Cfg.Cfg["/servers_updating/servers_autoupdate_timeout"] + if !ok2 { + o.servers_autoupdate_timeout.SetText("10") + } else { + o.servers_autoupdate_timeout.SetText(servers_update_timeout) + } + } // Appearance tab initialization. @@ -177,6 +207,54 @@ func (o *OptionsDialog) initializeGeneralTab() { o.tab_widget.AppendPage(general_vbox, gtk.NewLabel("General")) } +func (o *OptionsDialog) initializeServersOptionsTab() { + servers_options_vbox := gtk.NewVBox(false, 0) + + servers_updating_table := gtk.NewTable(3, 2, false) + servers_updating_table.SetRowSpacings(2) + + // Master server address. + master_server_addr_tooltip := "Address of master server. Specify in form: addr:port." + master_server_addr_label := gtk.NewLabel("Master server address") + master_server_addr_label.SetTooltipText(master_server_addr_tooltip) + master_server_addr_label.SetAlignment(0, 0) + servers_updating_table.Attach(master_server_addr_label, 0, 1, 0, 1, gtk.FILL, gtk.SHRINK, 5, 5) + + o.master_server_addr = gtk.NewEntry() + o.master_server_addr.SetTooltipText(master_server_addr_tooltip) + servers_updating_table.Attach(o.master_server_addr, 1, 2, 0, 1, gtk.FILL, gtk.FILL, 5, 5) + + // Servers autoupdate checkbox. + servers_autoupdate_cb_tooptip := "Should servers be automatically updated?" + servers_autoupdate_cb_label := gtk.NewLabel("Servers autoupdate") + servers_autoupdate_cb_label.SetTooltipText(servers_autoupdate_cb_tooptip) + servers_autoupdate_cb_label.SetAlignment(0, 0) + servers_updating_table.Attach(servers_autoupdate_cb_label, 0, 1 ,1, 2, gtk.FILL, gtk.SHRINK, 5, 5) + + o.servers_autoupdate = gtk.NewCheckButtonWithLabel("") + o.servers_autoupdate.SetTooltipText(servers_autoupdate_cb_tooptip) + servers_updating_table.Attach(o.servers_autoupdate, 1, 2, 1, 2, gtk.FILL, gtk.FILL, 5, 5) + + // Servers update timeout. + servers_autoupdate_timeout_tooltip := "Timeout which will trigger servers information update, in minutes." + servers_autoupdate_label := gtk.NewLabel("Servers update timeout (minutes)") + servers_autoupdate_label.SetTooltipText(servers_autoupdate_timeout_tooltip) + servers_autoupdate_label.SetAlignment(0, 0) + servers_updating_table.Attach(servers_autoupdate_label, 0, 1, 2, 3, gtk.FILL, gtk.SHRINK, 5, 5) + + o.servers_autoupdate_timeout = gtk.NewEntry() + o.servers_autoupdate_timeout.SetTooltipText(servers_autoupdate_timeout_tooltip) + servers_updating_table.Attach(o.servers_autoupdate_timeout, 1, 2, 2, 3, gtk.FILL, gtk.FILL, 5, 5) + + // Vertical separator. + sep := gtk.NewVBox(false, 0) + + servers_options_vbox.PackStart(servers_updating_table, false, true, 0) + servers_options_vbox.PackStart(sep, true, true, 0) + + o.tab_widget.AppendPage(servers_options_vbox, gtk.NewLabel("Servers updating")) +} + func (o *OptionsDialog) initializeStorages() { // Structure: // Name|Version|Second X session @@ -191,6 +269,7 @@ func (o *OptionsDialog) initializeTabs() { o.initializeGeneralTab() o.initializeAppearanceTab() o.initializeUrtTab() + o.initializeServersOptionsTab() // Buttons for saving and discarding changes. buttons_hbox := gtk.NewHBox(false, 0) @@ -278,7 +357,28 @@ func (o *OptionsDialog) saveGeneral() { ctx.Cfg.Cfg["/general/urtrator_autoupdate"] = "0" } - fmt.Println(ctx.Cfg.Cfg) + // Servers updating tab. + master_server_addr := o.master_server_addr.GetText() + if len(master_server_addr) < 1 { + ctx.Cfg.Cfg["/servers_updating/master_server"] = "master.urbanterror.info:27900" + } else { + ctx.Cfg.Cfg["/servers_updating/master_server"] = master_server_addr + } + + if o.servers_autoupdate.GetActive() { + ctx.Cfg.Cfg["/servers_updating/servers_autoupdate"] = "1" + } else { + ctx.Cfg.Cfg["/servers_updating/servers_autoupdate"] = "0" + } + + update_timeout := o.servers_autoupdate_timeout.GetText() + if len(update_timeout) < 1 { + ctx.Cfg.Cfg["/servers_updating/servers_autoupdate_timeout"] = "10" + } else { + ctx.Cfg.Cfg["/servers_updating/servers_autoupdate_timeout"] = update_timeout + } + + ctx.Eventer.LaunchEvent("initializeTasksForMainWindow", map[string]string{}) } func (o *OptionsDialog) ShowOptionsDialog() {