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.
This commit is contained in:
Azareal 2017-12-24 22:08:35 +00:00
parent c7df616f5b
commit 964d219407
11 changed files with 84 additions and 39 deletions

View File

@ -47,7 +47,7 @@ func routeSitemapXml(w http.ResponseWriter, r *http.Request) common.RouteError {
writeXMLHeader(w, r)
w.Write([]byte("<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n"))
sitemapItem("sitemaps/topics.xml")
sitemapItem("sitemaps/forums.xml")
//sitemapItem("sitemaps/forums.xml")
//sitemapItem("sitemaps/users.xml")
w.Write([]byte("</sitemapindex>"))

View File

@ -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++

View File

@ -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()

View File

@ -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)
}

28
main.go
View File

@ -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)
}()

View File

@ -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)
}

View File

@ -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
}

View File

@ -601,8 +601,7 @@ var profile_21 = []byte(`
<div id="profile_comments_head" class="colstack_item colstack_head hash_hide">
<div class="rowitem"><h1><a>Comments</a></h1></div>
</div>
<div id="profile_comments" class="colstack_item hash_hide">
`)
<div id="profile_comments" class="colstack_item hash_hide">`)
var profile_comments_row_0 = []byte(`
<div class="rowitem passive deletable_block editable_parent simple `)
var profile_comments_row_1 = []byte(`" style="background-image: url(`)
@ -672,8 +671,7 @@ var profile_comments_row_33 = []byte(`&type=user-reply"><button class="username
</div>
</div>
`)
var profile_22 = []byte(`
</div>
var profile_22 = []byte(`</div>
`)
var profile_23 = []byte(`
@ -682,9 +680,9 @@ var profile_23 = []byte(`
var profile_24 = []byte(`' type="hidden" />
<div class="colstack_item topic_reply_form" style="border-top: none;">
<div class="formrow">
<div class="formitem"><textarea name="reply-content" placeholder="Insert reply here"></textarea></div>
<div class="formitem"><textarea class="input_content" name="reply-content" placeholder="Insert comment here"></textarea></div>
</div>
<div class="formrow">
<div class="formrow quick_button_row">
<div class="formitem"><button name="reply-button" class="formbutton">Create Reply</button></div>
</div>
</div>

View File

@ -69,18 +69,16 @@
<div id="profile_comments_head" class="colstack_item colstack_head hash_hide">
<div class="rowitem"><h1><a>Comments</a></h1></div>
</div>
<div id="profile_comments" class="colstack_item hash_hide">
{{template "profile_comments_row.html" . }}
</div>
<div id="profile_comments" class="colstack_item hash_hide">{{template "profile_comments_row.html" . }}</div>
{{if not .CurrentUser.IsBanned}}
<form id="profile_comments_form" class="hash_hide" action="/profile/reply/create/" method="post">
<input name="uid" value='{{.ProfileOwner.ID}}' type="hidden" />
<div class="colstack_item topic_reply_form" style="border-top: none;">
<div class="formrow">
<div class="formitem"><textarea name="reply-content" placeholder="Insert reply here"></textarea></div>
<div class="formitem"><textarea class="input_content" name="reply-content" placeholder="Insert comment here"></textarea></div>
</div>
<div class="formrow">
<div class="formrow quick_button_row">
<div class="formitem"><button name="reply-button" class="formbutton">Create Reply</button></div>
</div>
</div>

View File

@ -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;
}

View File

@ -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(){