Added the Online Users Context widget.

Commented out a couple lines of debug code.
Fixed a data race where the Online Users widget would sometimes show more users than it should.
Added a new phrase.
This commit is contained in:
Azareal 2019-01-22 22:06:55 +10:00
parent dc8079f581
commit 89a5eafa48
6 changed files with 62 additions and 19 deletions

View File

@ -34,6 +34,7 @@ var errWsNouser = errors.New("This user isn't connected via WebSockets")
func init() { func init() {
adminStatsWatchers = make(map[*websocket.Conn]*WSUser) adminStatsWatchers = make(map[*websocket.Conn]*WSUser)
topicListWatchers = make(map[*WSUser]bool) topicListWatchers = make(map[*WSUser]bool)
topicWatchers = make(map[int]map[*WSUser]bool)
} }
type WsTopicList struct { type WsTopicList struct {
@ -122,6 +123,7 @@ func wsPageResponses(wsUser *WSUser, conn *websocket.Conn, page string) {
topicListMutex.Lock() topicListMutex.Lock()
topicListWatchers[wsUser] = true topicListWatchers[wsUser] = true
topicListMutex.Unlock() topicListMutex.Unlock()
// TODO: Evict from page when permissions change? Or check user perms every-time before sending data?
case strings.HasPrefix(page, "/topic/"): case strings.HasPrefix(page, "/topic/"):
//fmt.Println("entering topic prefix websockets zone") //fmt.Println("entering topic prefix websockets zone")
_, tid, err := ParseSEOURL(page) _, tid, err := ParseSEOURL(page)
@ -132,13 +134,12 @@ func wsPageResponses(wsUser *WSUser, conn *websocket.Conn, page string) {
if err != nil { if err != nil {
return return
} }
var usercpy *User = BlankUser()
*usercpy = *wsUser.User
usercpy.Init()
if !Forums.Exists(topic.ParentID) { if !Forums.Exists(topic.ParentID) {
return return
} }
var usercpy *User = BlankUser()
*usercpy = *wsUser.User
usercpy.Init()
/*skip, rerr := header.Hooks.VhookSkippable("ws_topic_check_pre_perms", w, r, usercpy, &fid, &header) /*skip, rerr := header.Hooks.VhookSkippable("ws_topic_check_pre_perms", w, r, usercpy, &fid, &header)
if skip || rerr != nil { if skip || rerr != nil {
@ -155,6 +156,14 @@ func wsPageResponses(wsUser *WSUser, conn *websocket.Conn, page string) {
if !usercpy.Perms.ViewTopic { if !usercpy.Perms.ViewTopic {
return return
} }
topicMutex.Lock()
_, ok := topicWatchers[topic.ID]
if !ok {
topicWatchers[topic.ID] = make(map[*WSUser]bool)
}
topicWatchers[topic.ID][wsUser] = true
topicMutex.Unlock()
case page == "/panel/": case page == "/panel/":
if !wsUser.User.IsSuperMod { if !wsUser.User.IsSuperMod {
return return
@ -180,9 +189,7 @@ func wsPageResponses(wsUser *WSUser, conn *websocket.Conn, page string) {
func wsLeavePage(wsUser *WSUser, conn *websocket.Conn, page string) { func wsLeavePage(wsUser *WSUser, conn *websocket.Conn, page string) {
if page == "/" { if page == "/" {
page = Config.DefaultPath page = Config.DefaultPath
} } else if page != "" {
if page != "" {
DebugLog("Leaving page " + page) DebugLog("Leaving page " + page)
} }
switch { switch {
@ -194,6 +201,26 @@ func wsLeavePage(wsUser *WSUser, conn *websocket.Conn, page string) {
}) })
case strings.HasPrefix(page, "/topic/"): case strings.HasPrefix(page, "/topic/"):
//fmt.Println("leaving topic prefix websockets zone") //fmt.Println("leaving topic prefix websockets zone")
wsUser.FinalizePage(page, func() {
_, tid, err := ParseSEOURL(page)
if err != nil {
return
}
topicMutex.Lock()
defer topicMutex.Unlock()
topic, ok := topicWatchers[tid]
if !ok {
return
}
_, ok = topic[wsUser]
if !ok {
return
}
delete(topic, wsUser)
if len(topic) == 0 {
delete(topicWatchers, tid)
}
})
case page == "/panel/": case page == "/panel/":
adminStatsMutex.Lock() adminStatsMutex.Lock()
delete(adminStatsWatchers, conn) delete(adminStatsWatchers, conn)
@ -209,6 +236,8 @@ func wsLeavePage(wsUser *WSUser, conn *websocket.Conn, page string) {
// TODO: Use odd-even sharding // TODO: Use odd-even sharding
var topicListWatchers map[*WSUser]bool var topicListWatchers map[*WSUser]bool
var topicListMutex sync.RWMutex var topicListMutex sync.RWMutex
var topicWatchers map[int]map[*WSUser]bool // map[tid]watchers
var topicMutex sync.RWMutex
var adminStatsWatchers map[*websocket.Conn]*WSUser var adminStatsWatchers map[*websocket.Conn]*WSUser
var adminStatsMutex sync.RWMutex var adminStatsMutex sync.RWMutex

View File

@ -25,6 +25,9 @@ func wolBuild(widget *Widget, hvars interface{}) (string, error) {
var users []*User var users []*User
if ucount < 30 { if ucount < 30 {
users = WsHub.AllUsers() users = WsHub.AllUsers()
if len(users) >= 30 {
users = nil
}
} }
wol := &wolUsers{hvars.(*Header), phrases.GetTmplPhrase("widget.online_name"), users, ucount} wol := &wolUsers{hvars.(*Header), phrases.GetTmplPhrase("widget.online_name"), users, ucount}
err := wol.Header.Theme.RunTmpl("widget_online", wol, wol.Header.Writer) err := wol.Header.Theme.RunTmpl("widget_online", wol, wol.Header.Writer)

View File

@ -3,13 +3,24 @@ package common
import "github.com/Azareal/Gosora/common/phrases" import "github.com/Azareal/Gosora/common/phrases"
func wolContextRender(widget *Widget, hvars interface{}) (string, error) { func wolContextRender(widget *Widget, hvars interface{}) (string, error) {
ucount := WsHub.UserCount() header := hvars.(*Header)
// We don't want a ridiculously long list, so we'll show the number if it's too high and only show staff individually if header.Zone != "view_topic" {
var users []*User return "", nil
if ucount < 30 {
users = WsHub.AllUsers()
} }
wol := &wolUsers{hvars.(*Header), phrases.GetTmplPhrase("widget.online_name"), users, ucount} var ucount int
err := wol.Header.Theme.RunTmpl("widget_online", wol, wol.Header.Writer) var users []*User
topicMutex.RLock()
topic, ok := topicWatchers[header.ZoneID]
if ok {
ucount = len(topic)
if ucount < 30 {
for wsUser, _ := range topic {
users = append(users, wsUser.User)
}
}
}
topicMutex.RUnlock()
wol := &wolUsers{header, phrases.GetTmplPhrase("widget.online_view_topic_name"), users, ucount}
err := header.Theme.RunTmpl("widget_online", wol, header.Writer)
return "", err return "", err
} }

View File

@ -669,6 +669,7 @@
"widget.online_name":"Online Users", "widget.online_name":"Online Users",
"widget.online_none_online":"No one is online.", "widget.online_none_online":"No one is online.",
"widget.online_some_online":"There are %d users online.", "widget.online_some_online":"There are %d users online.",
"widget.online_view_topic_name":"In Topic",
"option_yes":"Yes", "option_yes":"Yes",
"option_no":"No", "option_no":"No",

View File

@ -4,7 +4,6 @@ import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@ -386,7 +385,7 @@ func widgetsParseInputs(r *http.Request, widget *common.Widget) (*common.WidgetE
return nil, errors.New("You need to fill in the body for this widget.") return nil, errors.New("You need to fill in the body for this widget.")
} }
widget.Type = wtype // ? - Are we sure we should be directly assigning user provided data even if it's validated? widget.Type = wtype // ? - Are we sure we should be directly assigning user provided data even if it's validated?
case "wol", "search_and_filter": case "wol", "wol_context", "search_and_filter":
widget.Type = wtype // ? - Are we sure we should be directly assigning user provided data even if it's validated? widget.Type = wtype // ? - Are we sure we should be directly assigning user provided data even if it's validated?
default: default:
return nil, errors.New("Unknown widget type") return nil, errors.New("Unknown widget type")
@ -397,7 +396,7 @@ func widgetsParseInputs(r *http.Request, widget *common.Widget) (*common.WidgetE
// ThemesWidgetsEditSubmit is an action which is triggered when someone sends an update request for a widget // ThemesWidgetsEditSubmit is an action which is triggered when someone sends an update request for a widget
func ThemesWidgetsEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, swid string) common.RouteError { func ThemesWidgetsEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, swid string) common.RouteError {
fmt.Println("in ThemesWidgetsEditSubmit") //fmt.Println("in ThemesWidgetsEditSubmit")
_, ferr := common.SimplePanelUserCheck(w, r, &user) _, ferr := common.SimplePanelUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {
return ferr return ferr
@ -434,7 +433,7 @@ func ThemesWidgetsEditSubmit(w http.ResponseWriter, r *http.Request, user common
// ThemesWidgetsCreateSubmit is an action which is triggered when someone sends a create request for a widget // ThemesWidgetsCreateSubmit is an action which is triggered when someone sends a create request for a widget
func ThemesWidgetsCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func ThemesWidgetsCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
fmt.Println("in ThemesWidgetsCreateSubmit") //fmt.Println("in ThemesWidgetsCreateSubmit")
isJs := (r.PostFormValue("js") == "1") isJs := (r.PostFormValue("js") == "1")
_, ferr := common.SimplePanelUserCheck(w, r, &user) _, ferr := common.SimplePanelUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {

View File

@ -5,7 +5,7 @@
<option value="about"{{if eq .Type "about"}} selected{{end}}>{{lang "panel_themes_widgets_type_about"}}</option> <option value="about"{{if eq .Type "about"}} selected{{end}}>{{lang "panel_themes_widgets_type_about"}}</option>
<option value="simple"{{if eq .Type "simple"}} selected{{end}}>{{lang "panel_themes_widgets_type_simple"}}</option> <option value="simple"{{if eq .Type "simple"}} selected{{end}}>{{lang "panel_themes_widgets_type_simple"}}</option>
<option value="wol"{{if eq .Type "wol"}} selected{{end}}>{{lang "panel_themes_widgets_type_wol"}}</option> <option value="wol"{{if eq .Type "wol"}} selected{{end}}>{{lang "panel_themes_widgets_type_wol"}}</option>
<!--<option value="wol_context"{{if eq .Type "wol_context"}} selected{{end}}>{{lang "panel_themes_widgets_type_wol_context"}}</option>--> <option value="wol_context"{{if eq .Type "wol_context"}} selected{{end}}>{{lang "panel_themes_widgets_type_wol_context"}}</option>
<!--<option value="search_and_filter"{{if eq .Type "search_and_filter"}} selected{{end}}>{{lang "panel_themes_widgets_type_search_and_filter"}}</option>--> <!--<option value="search_and_filter"{{if eq .Type "search_and_filter"}} selected{{end}}>{{lang "panel_themes_widgets_type_search_and_filter"}}</option>-->
</select> </select>
</div> </div>