2018-07-29 04:17:17 +00:00
package main
import (
"errors"
"log"
"time"
2019-05-09 06:58:55 +00:00
"strconv"
"sync/atomic"
2019-05-06 04:04:00 +00:00
"database/sql"
2018-07-29 04:17:17 +00:00
2019-04-19 08:20:10 +00:00
c "github.com/Azareal/Gosora/common"
2019-05-06 04:04:00 +00:00
"github.com/Azareal/Gosora/query_gen"
2018-07-29 04:17:17 +00:00
)
// TODO: Name the tasks so we can figure out which one it was when something goes wrong? Or maybe toss it up WithStack down there?
func runTasks ( tasks [ ] func ( ) error ) {
for _ , task := range tasks {
err := task ( )
if err != nil {
2019-04-19 08:20:10 +00:00
c . LogError ( err )
2018-07-29 04:17:17 +00:00
}
}
}
func startTick ( ) ( abort bool ) {
2019-04-19 08:20:10 +00:00
var isDBDown = atomic . LoadInt32 ( & c . IsDBDown )
2018-07-29 04:17:17 +00:00
err := db . Ping ( )
if err != nil {
// TODO: There's a bit of a race here, but it doesn't matter if this error appears multiple times in the logs as it's capped at three times, we just want to cut it down 99% of the time
if isDBDown == 0 {
2019-02-10 09:32:05 +00:00
db . SetConnMaxLifetime ( time . Second ) // Drop all the connections and start over
2019-04-19 08:20:10 +00:00
c . LogWarning ( err )
c . LogWarning ( errors . New ( "The database is down" ) )
2018-07-29 04:17:17 +00:00
}
2019-04-19 08:20:10 +00:00
atomic . StoreInt32 ( & c . IsDBDown , 1 )
2018-07-29 04:17:17 +00:00
return true
}
if isDBDown == 1 {
log . Print ( "The database is back" )
}
2019-02-10 09:32:05 +00:00
//db.SetConnMaxLifetime(time.Second * 60 * 5) // Make this infinite as the temporary lifetime change will purge the stale connections?
db . SetConnMaxLifetime ( - 1 )
2019-04-19 08:20:10 +00:00
atomic . StoreInt32 ( & c . IsDBDown , 0 )
2018-07-29 04:17:17 +00:00
return false
}
func runHook ( name string ) {
2019-04-19 08:20:10 +00:00
err := c . RunTaskHook ( name )
2018-07-29 04:17:17 +00:00
if err != nil {
2019-04-19 08:20:10 +00:00
c . LogError ( err , "Failed at task '" + name + "'" )
2018-07-29 04:17:17 +00:00
}
}
2018-12-31 09:03:49 +00:00
func tickLoop ( thumbChan chan bool ) {
2019-05-09 06:58:55 +00:00
lastDailyStr , err := c . Meta . Get ( "lastDaily" )
// TODO: Report this error back correctly...
if err != nil && err != sql . ErrNoRows {
c . LogError ( err )
}
lastDaily , _ := strconv . ParseInt ( lastDailyStr , 10 , 64 )
now := time . Now ( ) . Unix ( )
low := now - ( 60 * 60 * 24 )
if lastDaily < low {
dailies ( )
}
2018-12-31 09:03:49 +00:00
// TODO: Write tests for these
// Run this goroutine once every half second
halfSecondTicker := time . NewTicker ( time . Second / 2 )
secondTicker := time . NewTicker ( time . Second )
fifteenMinuteTicker := time . NewTicker ( 15 * time . Minute )
hourTicker := time . NewTicker ( time . Hour )
2019-05-06 04:04:00 +00:00
dailyTicker := time . NewTicker ( time . Hour * 24 )
2018-07-29 04:17:17 +00:00
for {
select {
case <- halfSecondTicker . C :
if startTick ( ) {
continue
}
runHook ( "before_half_second_tick" )
2019-04-19 08:20:10 +00:00
runTasks ( c . ScheduledHalfSecondTasks )
2018-07-29 04:17:17 +00:00
runHook ( "after_half_second_tick" )
case <- secondTicker . C :
if startTick ( ) {
continue
}
runHook ( "before_second_tick" )
go func ( ) { thumbChan <- true } ( )
2019-04-19 08:20:10 +00:00
runTasks ( c . ScheduledSecondTasks )
2018-07-29 04:17:17 +00:00
// TODO: Stop hard-coding this
2019-04-19 08:20:10 +00:00
err := c . HandleExpiredScheduledGroups ( )
2018-07-29 04:17:17 +00:00
if err != nil {
2019-04-19 08:20:10 +00:00
c . LogError ( err )
2018-07-29 04:17:17 +00:00
}
// TODO: Handle delayed moderation tasks
// Sync with the database, if there are any changes
2019-04-19 08:20:10 +00:00
err = c . HandleServerSync ( )
2018-07-29 04:17:17 +00:00
if err != nil {
2019-04-19 08:20:10 +00:00
c . LogError ( err )
2018-07-29 04:17:17 +00:00
}
// 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
runHook ( "after_second_tick" )
case <- fifteenMinuteTicker . C :
if startTick ( ) {
continue
}
runHook ( "before_fifteen_minute_tick" )
2019-04-19 08:20:10 +00:00
runTasks ( c . ScheduledFifteenMinuteTasks )
2018-07-29 04:17:17 +00:00
// TODO: Automatically lock topics, if they're really old, and the associated setting is enabled.
// TODO: Publish scheduled posts.
runHook ( "after_fifteen_minute_tick" )
case <- hourTicker . C :
if startTick ( ) {
continue
}
runHook ( "before_hour_tick" )
2019-04-19 08:20:10 +00:00
jsToken , err := c . GenerateSafeString ( 80 )
2018-07-29 04:17:17 +00:00
if err != nil {
2019-04-19 08:20:10 +00:00
c . LogError ( err )
2018-07-29 04:17:17 +00:00
}
2019-04-19 08:20:10 +00:00
c . JSTokenBox . Store ( jsToken )
2018-07-29 04:17:17 +00:00
2019-04-19 08:20:10 +00:00
c . OldSessionSigningKeyBox . Store ( c . SessionSigningKeyBox . Load ( ) . ( string ) ) // TODO: We probably don't need this type conversion
sessionSigningKey , err := c . GenerateSafeString ( 80 )
2018-07-29 04:17:17 +00:00
if err != nil {
2019-04-19 08:20:10 +00:00
c . LogError ( err )
2018-07-29 04:17:17 +00:00
}
2019-04-19 08:20:10 +00:00
c . SessionSigningKeyBox . Store ( sessionSigningKey )
2018-07-29 04:17:17 +00:00
2019-04-19 08:20:10 +00:00
runTasks ( c . ScheduledHourTasks )
2018-07-29 04:17:17 +00:00
runHook ( "after_hour_tick" )
2019-05-06 04:04:00 +00:00
// TODO: Handle the instance going down a lot better
case <- dailyTicker . C :
2019-05-09 06:58:55 +00:00
dailies ( )
2018-07-29 04:17:17 +00:00
}
// TODO: Handle the daily clean-up.
}
}
2019-05-09 06:58:55 +00:00
func dailies ( ) {
// TODO: Find a more efficient way of doing this
err := qgen . NewAcc ( ) . Select ( "activity_stream" ) . Cols ( "asid" ) . EachInt ( func ( asid int ) error {
count , err := qgen . NewAcc ( ) . Count ( "activity_stream_matches" ) . Where ( "asid = " + strconv . Itoa ( asid ) ) . Total ( )
if err != sql . ErrNoRows {
return err
}
if count > 0 {
return nil
}
_ , err = qgen . NewAcc ( ) . Delete ( "activity_stream" ) . Where ( "asid = ?" ) . Run ( asid )
return err
} )
if err != nil && err != sql . ErrNoRows {
c . LogError ( err )
}
2019-06-05 04:57:10 +00:00
if c . Config . LogPruneCutoff > - 1 {
_ , err := qgen . NewAcc ( ) . Delete ( "login_logs" ) . DateOlderThan ( "doneAt" , c . Config . LogPruneCutoff , "day" ) . Run ( )
if err != nil {
c . LogError ( err )
}
_ , err = qgen . NewAcc ( ) . Delete ( "registration_logs" ) . DateOlderThan ( "doneAt" , c . Config . LogPruneCutoff , "day" ) . Run ( )
if err != nil {
c . LogError ( err )
}
}
2019-05-09 06:58:55 +00:00
if c . Config . PostIPCutoff > - 1 {
// TODO: Use unixtime to remove this MySQLesque logic?
_ , err := qgen . NewAcc ( ) . Update ( "topics" ) . Set ( "ipaddress = '0'" ) . DateOlderThan ( "createdAt" , c . Config . PostIPCutoff , "day" ) . Where ( "ipaddress != '0'" ) . Exec ( )
if err != nil {
c . LogError ( err )
}
_ , err = qgen . NewAcc ( ) . Update ( "replies" ) . Set ( "ipaddress = '0'" ) . DateOlderThan ( "createdAt" , c . Config . PostIPCutoff , "day" ) . Where ( "ipaddress != '0'" ) . Exec ( )
if err != nil {
c . LogError ( err )
}
// TODO: Find some way of purging the ip data in polls_votes without breaking any anti-cheat measures which might be running... maybe hash it instead?
_ , err = qgen . NewAcc ( ) . Update ( "users_replies" ) . Set ( "ipaddress = '0'" ) . DateOlderThan ( "createdAt" , c . Config . PostIPCutoff , "day" ) . Where ( "ipaddress != '0'" ) . Exec ( )
if err != nil {
c . LogError ( err )
}
// TODO: lastActiveAt isn't currently set, so we can't rely on this to purge last_ips of users who haven't been on in a while
/ * _ , err = qgen . NewAcc ( ) . Update ( "users" ) . Set ( "last_ip = '0'" ) . DateOlderThan ( "lastActiveAt" , c . Config . PostIPCutoff , "day" ) . Where ( "last_ip != '0'" ) . Exec ( )
if err != nil {
c . LogError ( err )
} * /
}
err = c . Meta . Set ( "lastDaily" , strconv . FormatInt ( time . Now ( ) . Unix ( ) , 10 ) )
if err != nil {
c . LogError ( err )
}
}