From 964d219407618528c1e09fdb6f0167762d0f9264 Mon Sep 17 00:00:00 2001 From: Azareal Date: Sun, 24 Dec 2017 22:08:35 +0000 Subject: [PATCH] Added support for per-topic view counters. Added support for shutdown tasks. View counters are now saved on graceful shutdown. Dynamic routes are now tracked by the route view counter. The uploads route should now be tracked by the route view counter. Added a WYSIWYG Editor to the profiles for Cosora. --- bot_routes.go | 2 +- common/counters.go | 35 ++++++++++++++++++++++++----------- common/tasks.go | 6 ++++++ gen_router.go | 10 +++++++++- main.go | 28 ++++++++++++++-------------- router_gen/main.go | 7 ++++++- routes.go | 7 +++++++ template_list.go | 10 ++++------ templates/profile.html | 8 +++----- themes/cosora/public/main.css | 6 ++++++ themes/cosora/public/misc.js | 4 ++++ 11 files changed, 84 insertions(+), 39 deletions(-) diff --git a/bot_routes.go b/bot_routes.go index a9299dba..4e26f376 100644 --- a/bot_routes.go +++ b/bot_routes.go @@ -47,7 +47,7 @@ func routeSitemapXml(w http.ResponseWriter, r *http.Request) common.RouteError { writeXMLHeader(w, r) w.Write([]byte("\n")) sitemapItem("sitemaps/topics.xml") - sitemapItem("sitemaps/forums.xml") + //sitemapItem("sitemaps/forums.xml") //sitemapItem("sitemaps/users.xml") w.Write([]byte("")) diff --git a/common/counters.go b/common/counters.go index 8853dee5..87932509 100644 --- a/common/counters.go +++ b/common/counters.go @@ -2,7 +2,6 @@ package common import ( "database/sql" - "log" "sync" "sync/atomic" @@ -28,6 +27,7 @@ func NewChunkedViewCounter() (*ChunkedViewCounter, error) { } AddScheduledFifteenMinuteTask(counter.Tick) // This is run once every fifteen minutes to match the frequency of the RouteViewCounter //AddScheduledSecondTask(counter.Tick) + AddShutdownTask(counter.Tick) return counter, acc.FirstError() } @@ -82,6 +82,7 @@ func NewDefaultRouteViewCounter() (*DefaultRouteViewCounter, error) { } AddScheduledFifteenMinuteTask(counter.Tick) // There could be a lot of routes, so we don't want to be running this every second //AddScheduledSecondTask(counter.Tick) + AddShutdownTask(counter.Tick) return counter, acc.FirstError() } @@ -113,7 +114,7 @@ func (counter *DefaultRouteViewCounter) insertChunk(count int, route int) error func (counter *DefaultRouteViewCounter) Bump(route int) { // TODO: Test this check - log.Print("counter.routeBuckets[route]: ", counter.routeBuckets[route]) + debugLog("counter.routeBuckets[", route, "]: ", counter.routeBuckets[route]) if len(counter.routeBuckets) <= route { return } @@ -157,41 +158,53 @@ func NewDefaultTopicViewCounter() (*DefaultTopicViewCounter, error) { evenTopics: make(map[int]*RWMutexCounterBucket), update: acc.Update("topics").Set("views = views + ?").Where("tid = ?").Prepare(), } - AddScheduledFifteenMinuteTask(counter.Tick) // There could be a lot of routes, so we don't want to be running this every second + AddScheduledFifteenMinuteTask(counter.Tick) // Who knows how many topics we have queued up, we probably don't want this running too frequently //AddScheduledSecondTask(counter.Tick) + AddShutdownTask(counter.Tick) return counter, acc.FirstError() } func (counter *DefaultTopicViewCounter) Tick() error { counter.oddLock.RLock() - for topicID, topic := range counter.oddTopics { + oddTopics := counter.oddTopics + counter.oddLock.RUnlock() + for topicID, topic := range oddTopics { var count int topic.RLock() count = topic.counter topic.RUnlock() + // TODO: Only delete the bucket when it's zero to avoid hitting popular topics? + counter.oddLock.Lock() + delete(counter.oddTopics, topicID) + counter.oddLock.Unlock() err := counter.insertChunk(count, topicID) if err != nil { return err } } - counter.oddLock.RUnlock() counter.evenLock.RLock() - for topicID, topic := range counter.evenTopics { + evenTopics := counter.evenTopics + counter.evenLock.RUnlock() + for topicID, topic := range evenTopics { var count int topic.RLock() count = topic.counter topic.RUnlock() + // TODO: Only delete the bucket when it's zero to avoid hitting popular topics? + counter.evenLock.Lock() + delete(counter.evenTopics, topicID) + counter.evenLock.Unlock() err := counter.insertChunk(count, topicID) if err != nil { return err } } - counter.evenLock.RUnlock() return nil } +// TODO: Optimise this further. E.g. Using IN() on every one view topic. Rinse and repeat for two views, three views, four views and five views. func (counter *DefaultTopicViewCounter) insertChunk(count int, topicID int) error { if count == 0 { return nil @@ -204,9 +217,9 @@ func (counter *DefaultTopicViewCounter) insertChunk(count int, topicID int) erro func (counter *DefaultTopicViewCounter) Bump(topicID int) { // Is the ID even? if topicID%2 == 0 { - counter.evenLock.Lock() + counter.evenLock.RLock() topic, ok := counter.evenTopics[topicID] - counter.evenLock.Unlock() + counter.evenLock.RUnlock() if ok { topic.Lock() topic.counter++ @@ -219,9 +232,9 @@ func (counter *DefaultTopicViewCounter) Bump(topicID int) { return } - counter.oddLock.Lock() + counter.oddLock.RLock() topic, ok := counter.oddTopics[topicID] - counter.oddLock.Unlock() + counter.oddLock.RUnlock() if ok { topic.Lock() topic.counter++ diff --git a/common/tasks.go b/common/tasks.go index 513a13de..0069bb19 100644 --- a/common/tasks.go +++ b/common/tasks.go @@ -21,6 +21,7 @@ type TaskStmts struct { var ScheduledSecondTasks []func() error var ScheduledFifteenMinuteTasks []func() error +var ShutdownTasks []func() error var taskStmts TaskStmts var lastSync time.Time @@ -45,6 +46,11 @@ func AddScheduledFifteenMinuteTask(task func() error) { ScheduledFifteenMinuteTasks = append(ScheduledFifteenMinuteTasks, task) } +// AddShutdownTask is not concurrency safe +func AddShutdownTask(task func() error) { + ShutdownTasks = append(ShutdownTasks, task) +} + // TODO: Use AddScheduledSecondTask func HandleExpiredScheduledGroups() error { rows, err := taskStmts.getExpiredScheduledGroups.Query() diff --git a/gen_router.go b/gen_router.go index 3d0ee519..8132033d 100644 --- a/gen_router.go +++ b/gen_router.go @@ -72,6 +72,8 @@ var RouteMap = map[string]interface{}{ "routeUnban": routeUnban, "routeActivate": routeActivate, "routeIps": routeIps, + "routeDynamic": routeDynamic, + "routeUploads": routeUploads, } // ! NEVER RELY ON THESE REMAINING THE SAME BETWEEN COMMITS @@ -133,6 +135,8 @@ var routeMapEnum = map[string]int{ "routeUnban": 54, "routeActivate": 55, "routeIps": 56, + "routeDynamic": 57, + "routeUploads": 58, } var reverseRouteMapEnum = map[int]string{ 0: "routeAPI", @@ -192,6 +196,8 @@ var reverseRouteMapEnum = map[int]string{ 54: "routeUnban", 55: "routeActivate", 56: "routeIps", + 57: "routeDynamic", + 58: "routeUploads", } // TODO: Stop spilling these into the package scope? @@ -794,6 +800,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { common.NotFound(w,req) return } + common.RouteViewCounter.Bump(58) req.URL.Path += extraData // TODO: Find a way to propagate errors up from this? router.UploadHandler(w,req) // TODO: Count these views @@ -837,8 +844,9 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { router.RUnlock() if ok { + common.RouteViewCounter.Bump(57) // TODO: Be more specific about *which* dynamic route it is req.URL.Path += extraData - err = handle(w,req,user) // TODO: Count these views + err = handle(w,req,user) if err != nil { router.handleError(err,w,req,user) } diff --git a/main.go b/main.go index e6128765..82c76032 100644 --- a/main.go +++ b/main.go @@ -234,6 +234,14 @@ func main() { } } + var runTasks = func(tasks []func() error) { + for _, task := range tasks { + if task() != nil { + common.LogError(err) + } + } + } + // Run this goroutine once a second secondTicker := time.NewTicker(1 * time.Second) fifteenMinuteTicker := time.NewTicker(15 * time.Minute) @@ -242,22 +250,16 @@ func main() { for { select { case <-secondTicker.C: - //log.Print("Running the second ticker") // TODO: Add a plugin hook here + runTasks(common.ScheduledSecondTasks) - for _, task := range common.ScheduledSecondTasks { - if task() != nil { - common.LogError(err) - } - } - + // TODO: Stop hard-coding this err := common.HandleExpiredScheduledGroups() if err != nil { common.LogError(err) } // TODO: Handle delayed moderation tasks - // TODO: Handle the daily clean-up. Move this to a 24 hour task? // Sync with the database, if there are any changes err = common.HandleServerSync() @@ -273,18 +275,15 @@ func main() { // TODO: Add a plugin hook here case <-fifteenMinuteTicker.C: // TODO: Add a plugin hook here - - for _, task := range common.ScheduledFifteenMinuteTasks { - if task() != nil { - common.LogError(err) - } - } + runTasks(common.ScheduledFifteenMinuteTasks) // TODO: Automatically lock topics, if they're really old, and the associated setting is enabled. // TODO: Publish scheduled posts. // TODO: Add a plugin hook here } + + // TODO: Handle the daily clean-up. } }() @@ -329,6 +328,7 @@ func main() { go func() { sig := <-sigs // TODO: Gracefully shutdown the HTTP server + runTasks(common.ShutdownTasks) log.Fatal("Received a signal to shutdown: ", sig) }() diff --git a/router_gen/main.go b/router_gen/main.go index 72db1971..c4b272c1 100644 --- a/router_gen/main.go +++ b/router_gen/main.go @@ -150,6 +150,9 @@ func main() { }` } + // Stubs for us to refer to these routes through + mapIt("routeDynamic") + mapIt("routeUploads") tmplVars.AllRouteNames = allRouteNames tmplVars.AllRouteMap = allRouteMap @@ -294,6 +297,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { common.NotFound(w,req) return } + common.RouteViewCounter.Bump({{.AllRouteMap.routeUploads}}) req.URL.Path += extraData // TODO: Find a way to propagate errors up from this? router.UploadHandler(w,req) // TODO: Count these views @@ -337,8 +341,9 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { router.RUnlock() if ok { + common.RouteViewCounter.Bump({{.AllRouteMap.routeDynamic}}) // TODO: Be more specific about *which* dynamic route it is req.URL.Path += extraData - err = handle(w,req,user) // TODO: Count these views + err = handle(w,req,user) if err != nil { router.handleError(err,w,req,user) } diff --git a/routes.go b/routes.go index bd39a64e..24efdc80 100644 --- a/routes.go +++ b/routes.go @@ -40,6 +40,12 @@ func (red *HTTPSRedirect) ServeHTTP(w http.ResponseWriter, req *http.Request) { http.Redirect(w, req, dest, http.StatusTemporaryRedirect) } +// Temporary stubs for view tracking +func routeDynamic() { +} +func routeUploads() { +} + // GET functions func routeStatic(w http.ResponseWriter, r *http.Request) { file, ok := common.StaticFiles.Get(r.URL.Path) @@ -613,6 +619,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user common.User) comm if err != nil { return common.InternalError(err, w, r) } + common.TopicViewCounter.Bump(topic.ID) // TODO Move this into the router? return nil } diff --git a/template_list.go b/template_list.go index 0d43f1c6..4f08e252 100644 --- a/template_list.go +++ b/template_list.go @@ -601,8 +601,7 @@ var profile_21 = []byte(`
-
- `) +
`) var profile_comments_row_0 = []byte(`
diff --git a/templates/profile.html b/templates/profile.html index bf84108a..0648f223 100644 --- a/templates/profile.html +++ b/templates/profile.html @@ -69,18 +69,16 @@
-
- {{template "profile_comments_row.html" . }} -
+
{{template "profile_comments_row.html" . }}
{{if not .CurrentUser.IsBanned}}
-
+
-
+
diff --git a/themes/cosora/public/main.css b/themes/cosora/public/main.css index c00acdc7..b57df627 100644 --- a/themes/cosora/public/main.css +++ b/themes/cosora/public/main.css @@ -479,6 +479,9 @@ select, input, textarea, button { width: 100%; height: min-content; } +.topic_reply_form .formrow { + padding: 0px !important; +} .topic_reply_form .trumbowyg-button-pane:after { display: none; } @@ -935,6 +938,9 @@ select, input, textarea, button { #profile_comments { margin-bottom: 12px; } +#profile_comments:empty { + display: none !important; +} #profile_comments .rowitem { background-image: none !important; } diff --git a/themes/cosora/public/misc.js b/themes/cosora/public/misc.js index 759806b8..157fc4e8 100644 --- a/themes/cosora/public/misc.js +++ b/themes/cosora/public/misc.js @@ -25,6 +25,10 @@ $(document).ready(function(){ btns: btnlist, autogrow: true, }); + $('#profile_comments_form .topic_reply_form .input_content').trumbowyg({ + btns: [['viewHTML'],['strong','em','del'],['link'],['insertImage'],['removeformat']], + autogrow: true, + }); // TODO: Refactor this to use `each` less $('.button_menu').click(function(){