2018-07-29 04:17:17 +00:00
package main
import (
2020-01-02 05:28:36 +00:00
"database/sql"
2018-07-29 04:17:17 +00:00
"errors"
"log"
2019-05-09 06:58:55 +00:00
"strconv"
"sync/atomic"
2020-01-02 05:28:36 +00:00
"time"
2018-07-29 04:17:17 +00:00
2019-04-19 08:20:10 +00:00
c "github.com/Azareal/Gosora/common"
2020-01-02 05:28:36 +00:00
qgen "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 {
2019-10-06 00:34:09 +00:00
if err := task ( ) ; 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-07-26 22:36:06 +00:00
isDBDown := atomic . LoadInt32 ( & c . IsDBDown )
2019-10-06 00:34:09 +00:00
if err := db . Ping ( ) ; err != nil {
2018-07-29 04:17:17 +00:00
// 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-10-06 00:34:09 +00:00
if err := c . RunTaskHook ( name ) ; 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 )
2019-07-26 22:36:06 +00:00
low := time . Now ( ) . Unix ( ) - ( 60 * 60 * 24 )
2019-05-09 06:58:55 +00:00
if lastDaily < low {
dailies ( )
}
2020-01-02 05:28:36 +00:00
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-10-06 00:34:09 +00:00
if err := c . HandleExpiredScheduledGroups ( ) ; 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-10-06 00:34:09 +00:00
if err = c . HandleServerSync ( ) ; 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
2019-12-31 21:57:54 +00:00
func asmMatches ( ) {
2019-05-09 06:58:55 +00:00
// TODO: Find a more efficient way of doing this
2019-12-31 21:57:54 +00:00
acc := qgen . NewAcc ( )
countStmt := acc . Count ( "activity_stream_matches" ) . Where ( "asid=?" ) . Prepare ( )
if err := acc . FirstError ( ) ; err != nil {
c . LogError ( err )
return
}
err := acc . Select ( "activity_stream" ) . Cols ( "asid" ) . EachInt ( func ( asid int ) error {
var count int
err := countStmt . QueryRow ( asid ) . Scan ( & count )
2019-05-09 06:58:55 +00:00
if err != sql . ErrNoRows {
return err
}
if count > 0 {
return nil
}
2019-12-31 21:57:54 +00:00
_ , err = qgen . NewAcc ( ) . Delete ( "activity_stream" ) . Where ( "asid=?" ) . Run ( asid )
2019-05-09 06:58:55 +00:00
return err
} )
if err != nil && err != sql . ErrNoRows {
c . LogError ( err )
}
2019-12-31 21:57:54 +00:00
}
func dailies ( ) {
asmMatches ( )
2019-05-09 06:58:55 +00:00
2019-06-05 04:57:10 +00:00
if c . Config . LogPruneCutoff > - 1 {
2019-10-06 00:34:09 +00:00
f := func ( tbl string ) {
2020-01-02 05:28:36 +00:00
_ , err := qgen . NewAcc ( ) . Delete ( tbl ) . DateOlderThan ( "doneAt" , c . Config . LogPruneCutoff , "day" ) . Run ( )
2019-10-06 00:34:09 +00:00
if err != nil {
c . LogError ( err )
}
2019-06-05 04:57:10 +00:00
}
2019-10-06 00:34:09 +00:00
f ( "login_logs" )
f ( "registration_logs" )
2019-06-05 04:57:10 +00:00
}
2019-05-09 06:58:55 +00:00
if c . Config . PostIPCutoff > - 1 {
// TODO: Use unixtime to remove this MySQLesque logic?
2019-10-06 00:34:09 +00:00
f := func ( tbl string ) {
2020-01-02 05:28:36 +00:00
_ , err := qgen . NewAcc ( ) . Update ( tbl ) . Set ( "ipaddress='0'" ) . DateOlderThan ( "createdAt" , c . Config . PostIPCutoff , "day" ) . Where ( "ipaddress!='0'" ) . Exec ( )
2019-10-06 00:34:09 +00:00
if err != nil {
c . LogError ( err )
}
2019-05-09 06:58:55 +00:00
}
2019-10-06 00:34:09 +00:00
f ( "topics" )
f ( "replies" )
f ( "users_replies" )
2020-01-02 05:28:36 +00:00
}
2020-01-02 09:49:34 +00:00
if c . Config . DisablePollIP {
_ , err := qgen . NewAcc ( ) . Update ( "polls_votes" ) . Set ( "ipaddress='0'" ) . Where ( "ipaddress!='0'" ) . Exec ( )
if err != nil {
c . LogError ( err )
}
} else if c . Config . PollIPCutoff > - 1 {
2020-01-02 05:28:36 +00:00
// TODO: Use unixtime to remove this MySQLesque logic?
_ , err := qgen . NewAcc ( ) . Update ( "polls_votes" ) . Set ( "ipaddress='0'" ) . DateOlderThan ( "castAt" , c . Config . PollIPCutoff , "day" ) . Where ( "ipaddress!='0'" ) . Exec ( )
if err != nil {
c . LogError ( err )
}
2019-05-09 06:58:55 +00:00
// 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?
2019-12-31 21:57:54 +00:00
}
2019-05-09 06:58:55 +00:00
2019-12-31 21:57:54 +00:00
// 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
2020-01-02 06:04:10 +00:00
if c . Config . DisableLastIP {
2019-12-31 21:57:54 +00:00
_ , err := qgen . NewAcc ( ) . Update ( "users" ) . Set ( "last_ip=0" ) . Where ( "last_ip!=0" ) . Exec ( )
if err != nil {
c . LogError ( err )
}
2020-01-02 06:04:10 +00:00
} else if c . Config . LastIPCutoff > 0 {
2019-12-31 21:57:54 +00:00
/ * _ , err = qgen . NewAcc ( ) . Update ( "users" ) . Set ( "last_ip='0'" ) . DateOlderThan ( "lastActiveAt" , c . Config . PostIPCutoff , "day" ) . Where ( "last_ip!='0'" ) . Exec ( )
2019-05-09 06:58:55 +00:00
if err != nil {
c . LogError ( err )
} * /
2019-12-31 21:57:54 +00:00
mon := time . Now ( ) . Month ( )
2020-01-02 05:28:36 +00:00
_ , err := qgen . NewAcc ( ) . Update ( "users" ) . Set ( "last_ip=0" ) . Where ( "last_ip!=0 AND last_ip NOT LIKE '" + strconv . Itoa ( int ( mon ) ) + "-%'" ) . Exec ( )
2019-12-31 21:57:54 +00:00
if err != nil {
c . LogError ( err )
}
2019-05-09 06:58:55 +00:00
}
2019-12-31 21:57:54 +00:00
{
err := c . Meta . Set ( "lastDaily" , strconv . FormatInt ( time . Now ( ) . Unix ( ) , 10 ) )
if err != nil {
c . LogError ( err )
}
2019-05-09 06:58:55 +00:00
}
2020-01-02 05:28:36 +00:00
}