300 lines
7.7 KiB
Go
300 lines
7.7 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"database/sql"
|
|
"log"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"time"
|
|
|
|
c "git.tuxpa.in/a/gosora/common"
|
|
"git.tuxpa.in/a/gosora/routes"
|
|
"git.tuxpa.in/a/gosora/uutils"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var TickLoop *c.TickLoop
|
|
|
|
func runHook(name string) error {
|
|
if e := c.RunTaskHook(name); e != nil {
|
|
return errors.Wrap(e, "Failed at task '"+name+"'")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func deferredDailies() error {
|
|
lastDailyStr, e := c.Meta.Get("lastDaily")
|
|
// TODO: Report this error back correctly...
|
|
if e != nil && e != sql.ErrNoRows {
|
|
return e
|
|
}
|
|
lastDaily, _ := strconv.ParseInt(lastDailyStr, 10, 64)
|
|
low := time.Now().Unix() - (60 * 60 * 24)
|
|
if lastDaily < low {
|
|
if e := c.Dailies(); e != nil {
|
|
return e
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func handleLogLongTick(name string, cn int64, secs int) {
|
|
if !c.Dev.LogLongTick {
|
|
return
|
|
}
|
|
dur := time.Duration(uutils.Nanotime() - cn)
|
|
if dur.Seconds() > float64(secs) {
|
|
log.Print("tick " + name + " completed in " + dur.String())
|
|
}
|
|
}
|
|
|
|
func tickLoop(thumbChan chan bool) error {
|
|
tl := c.NewTickLoop()
|
|
TickLoop = tl
|
|
if e := deferredDailies(); e != nil {
|
|
return e
|
|
}
|
|
if e := c.StartupTasks(); e != nil {
|
|
return e
|
|
}
|
|
|
|
startTick := func(ch chan bool) (ret bool) {
|
|
if c.Dev.HourDBTimeout {
|
|
go func() {
|
|
defer c.EatPanics()
|
|
ch <- c.StartTick()
|
|
}()
|
|
return <-ch
|
|
}
|
|
return c.StartTick()
|
|
}
|
|
tick := func(name string, tasks c.TaskSet, secs int) error {
|
|
tw := c.NewTickWatch()
|
|
tw.Name = name
|
|
tw.Set(&tw.Start, uutils.Nanotime())
|
|
tw.Run()
|
|
defer tw.Stop()
|
|
ch := make(chan bool)
|
|
tw.OutEndChan = ch
|
|
if startTick(ch) {
|
|
return nil
|
|
}
|
|
tw.Set(&tw.DBCheck, uutils.Nanotime())
|
|
if e := runHook("before_" + name + "_tick"); e != nil {
|
|
return e
|
|
}
|
|
cn := uutils.Nanotime()
|
|
tw.Set(&tw.StartHook, cn)
|
|
if e := tasks.Run(); e != nil {
|
|
return e
|
|
}
|
|
tw.Set(&tw.Tasks, uutils.Nanotime())
|
|
handleLogLongTick(name, cn, secs)
|
|
if e := runHook("after_" + name + "_tick"); e != nil {
|
|
return e
|
|
}
|
|
tw.Set(&tw.EndHook, uutils.Nanotime())
|
|
//close(tw.OutEndChan)
|
|
return nil
|
|
}
|
|
|
|
tl.HalfSecf = func() error {
|
|
return tick("half_second", c.Tasks.HalfSec, 2)
|
|
}
|
|
// TODO: Automatically lock topics, if they're really old, and the associated setting is enabled.
|
|
// TODO: Publish scheduled posts.
|
|
tl.FifteenMinf = func() error {
|
|
return tick("fifteen_minute", c.Tasks.FifteenMin, 5)
|
|
}
|
|
// TODO: Handle the instance going down a lot better
|
|
// TODO: Handle the daily clean-up.
|
|
tl.Dayf = func() error {
|
|
if c.StartTick() {
|
|
return nil
|
|
}
|
|
cn := uutils.Nanotime()
|
|
if e := c.Dailies(); e != nil {
|
|
return e
|
|
}
|
|
handleLogLongTick("day", cn, 5)
|
|
return nil
|
|
}
|
|
|
|
tl.Secf = func() (e error) {
|
|
if c.StartTick() {
|
|
return nil
|
|
}
|
|
if e = runHook("before_second_tick"); e != nil {
|
|
return e
|
|
}
|
|
cn := uutils.Nanotime()
|
|
go func() {
|
|
defer c.EatPanics()
|
|
thumbChan <- true
|
|
}()
|
|
|
|
if e = c.Tasks.Sec.Run(); e != nil {
|
|
return e
|
|
}
|
|
|
|
// TODO: Stop hard-coding this
|
|
if e = c.HandleExpiredScheduledGroups(); e != nil {
|
|
return e
|
|
}
|
|
|
|
// TODO: Handle delayed moderation tasks
|
|
|
|
// Sync with the database, if there are any changes
|
|
if e = c.HandleServerSync(); e != nil {
|
|
return e
|
|
}
|
|
handleLogLongTick("second", cn, 3)
|
|
|
|
// TODO: Manage the TopicStore, UserStore, and ForumStore
|
|
// TODO: Alert the admin, if CPU usage, RAM usage, or the number of posts in the past second are too high
|
|
// TODO: Clean-up alerts with no unread matches which are over two weeks old. Move this to a 24 hour task?
|
|
// TODO: Rescan the static files for changes
|
|
return runHook("after_second_tick")
|
|
}
|
|
|
|
tl.Hourf = func() error {
|
|
if c.StartTick() {
|
|
return nil
|
|
}
|
|
if e := runHook("before_hour_tick"); e != nil {
|
|
return e
|
|
}
|
|
cn := uutils.Nanotime()
|
|
|
|
jsToken, e := c.GenerateSafeString(80)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
c.JSTokenBox.Store(jsToken)
|
|
|
|
c.OldSessionSigningKeyBox.Store(c.SessionSigningKeyBox.Load().(string)) // TODO: We probably don't need this type conversion
|
|
sessionSigningKey, e := c.GenerateSafeString(80)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
c.SessionSigningKeyBox.Store(sessionSigningKey)
|
|
|
|
if e = c.Tasks.Hour.Run(); e != nil {
|
|
return e
|
|
}
|
|
if e = PingLastTopicTick(); e != nil {
|
|
return e
|
|
}
|
|
handleLogLongTick("hour", cn, 5)
|
|
return runHook("after_hour_tick")
|
|
}
|
|
|
|
c.CTickLoop = tl
|
|
return nil
|
|
}
|
|
|
|
func sched() error {
|
|
ws := errors.WithStack
|
|
schedStr, err := c.Meta.Get("sched")
|
|
// TODO: Report this error back correctly...
|
|
if err != nil && err != sql.ErrNoRows {
|
|
return ws(err)
|
|
}
|
|
|
|
if schedStr == "recalc" {
|
|
log.Print("Cleaning up orphaned data.")
|
|
|
|
count, err := c.Recalc.Replies()
|
|
if err != nil {
|
|
return ws(err)
|
|
}
|
|
log.Printf("Deleted %d orphaned replies.", count)
|
|
|
|
count, err = c.Recalc.Forums()
|
|
if err != nil {
|
|
return ws(err)
|
|
}
|
|
log.Printf("Recalculated %d forum topic counts.", count)
|
|
|
|
count, err = c.Recalc.Subscriptions()
|
|
if err != nil {
|
|
return ws(err)
|
|
}
|
|
log.Printf("Deleted %d orphaned subscriptions.", count)
|
|
|
|
count, err = c.Recalc.ActivityStream()
|
|
if err != nil {
|
|
return ws(err)
|
|
}
|
|
log.Printf("Deleted %d orphaned activity stream items.", count)
|
|
|
|
err = c.Recalc.Users()
|
|
if err != nil {
|
|
return ws(err)
|
|
}
|
|
log.Print("Recalculated user post stats.")
|
|
|
|
count, err = c.Recalc.Attachments()
|
|
if err != nil {
|
|
return ws(err)
|
|
}
|
|
log.Printf("Deleted %d orphaned attachments.", count)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
var pingLastTopicCount = 1
|
|
|
|
// TODO: Move somewhere else
|
|
func PingLastTopicTick() error {
|
|
g, e := c.Groups.Get(c.GuestUser.Group)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
tList, _, _, e := c.TopicList.GetListByGroup(g, 1, 0, nil)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
if len(tList) == 0 {
|
|
return nil
|
|
}
|
|
w := httptest.NewRecorder()
|
|
sid := strconv.Itoa(tList[0].ID)
|
|
req := httptest.NewRequest("get", "/topic/"+sid, bytes.NewReader(nil))
|
|
cn := uutils.Nanotime()
|
|
|
|
// Deal with the session stuff, etc.
|
|
ucpy, ok := c.PreRoute(w, req)
|
|
if !ok {
|
|
return errors.New("preroute failed")
|
|
}
|
|
head, rerr := c.UserCheck(w, req, &ucpy)
|
|
if rerr != nil {
|
|
return errors.New(rerr.Error())
|
|
}
|
|
rerr = routes.ViewTopic(w, req, &ucpy, head, sid)
|
|
if rerr != nil {
|
|
return errors.New(rerr.Error())
|
|
}
|
|
/*if w.Code != 200 {
|
|
return errors.New("topic code not 200")
|
|
}*/
|
|
|
|
dur := time.Duration(uutils.Nanotime() - cn)
|
|
if dur.Seconds() > 5 {
|
|
c.Log("topic " + sid + " completed in " + dur.String())
|
|
} else if c.Dev.Log4thLongRoute {
|
|
pingLastTopicCount++
|
|
if pingLastTopicCount == 4 {
|
|
c.Log("topic " + sid + " completed in " + dur.String())
|
|
}
|
|
if pingLastTopicCount >= 4 {
|
|
pingLastTopicCount = 1
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|