2017-11-10 03:33:11 +00:00
package common
2017-09-03 04:50:31 +00:00
2017-11-11 23:34:27 +00:00
import (
"database/sql"
2017-11-23 05:37:08 +00:00
"errors"
2017-11-11 23:34:27 +00:00
"strconv"
"strings"
"sync/atomic"
"../query_gen/lib"
)
2016-12-21 02:30:32 +00:00
2017-11-13 09:23:43 +00:00
var SettingBox atomic . Value // An atomic value pointing to a SettingBox
2017-11-10 03:33:11 +00:00
// SettingMap is a map type specifically for holding the various settings admins set to toggle features on and off or to otherwise alter Gosora's behaviour from the Control Panel
type SettingMap map [ string ] interface { }
2017-09-03 04:50:31 +00:00
2017-11-13 09:23:43 +00:00
type SettingStore interface {
ParseSetting ( sname string , scontent string , stype string , sconstraint string ) string
BypassGet ( name string ) ( * Setting , error )
2017-11-23 05:37:08 +00:00
BypassGetAll ( name string ) ( [ ] * Setting , error )
2017-11-13 09:23:43 +00:00
}
2016-12-21 02:30:32 +00:00
2017-09-03 04:50:31 +00:00
type OptionLabel struct {
Label string
Value int
2016-12-21 02:30:32 +00:00
Selected bool
}
2016-12-09 13:46:29 +00:00
2017-09-03 04:50:31 +00:00
type Setting struct {
Name string
Content string
Type string
2016-12-21 02:30:32 +00:00
Constraint string
2016-12-09 13:46:29 +00:00
}
2017-11-11 23:34:27 +00:00
type SettingStmts struct {
2017-11-13 09:23:43 +00:00
getAll * sql . Stmt
get * sql . Stmt
2017-11-23 05:37:08 +00:00
update * sql . Stmt
2017-11-11 23:34:27 +00:00
}
var settingStmts SettingStmts
2016-12-21 02:30:32 +00:00
func init ( ) {
2017-11-10 03:33:11 +00:00
SettingBox . Store ( SettingMap ( make ( map [ string ] interface { } ) ) )
2017-11-12 03:29:05 +00:00
DbInits . Add ( func ( acc * qgen . Accumulator ) error {
2017-11-11 23:34:27 +00:00
settingStmts = SettingStmts {
2017-11-13 09:23:43 +00:00
getAll : acc . Select ( "settings" ) . Columns ( "name, content, type, constraints" ) . Prepare ( ) ,
get : acc . Select ( "settings" ) . Columns ( "content, type, constraints" ) . Where ( "name = ?" ) . Prepare ( ) ,
2017-11-23 05:37:08 +00:00
update : acc . Update ( "settings" ) . Set ( "content = ?" ) . Where ( "name = ?" ) . Prepare ( ) ,
2017-11-11 23:34:27 +00:00
}
return acc . FirstError ( )
} )
2016-12-21 02:30:32 +00:00
}
2017-06-14 09:55:47 +00:00
func LoadSettings ( ) error {
2017-11-23 05:37:08 +00:00
var sBox = SettingMap ( make ( map [ string ] interface { } ) )
settings , err := sBox . BypassGetAll ( )
2017-06-14 09:55:47 +00:00
if err != nil {
return err
}
2017-11-23 05:37:08 +00:00
for _ , setting := range settings {
err = sBox . ParseSetting ( setting . Name , setting . Content , setting . Type , setting . Constraint )
2017-06-14 09:55:47 +00:00
if err != nil {
return err
}
}
2017-08-20 09:39:02 +00:00
2017-11-10 03:33:11 +00:00
SettingBox . Store ( sBox )
2017-06-14 09:55:47 +00:00
return nil
}
2017-11-23 05:37:08 +00:00
// nolint
var ErrNotInteger = errors . New ( "You were supposed to enter an integer x.x" )
var ErrSettingNotInteger = errors . New ( "Only integers are allowed in this setting x.x" )
var ErrBadConstraintNotInteger = errors . New ( "Invalid contraint! The constraint field wasn't an integer!" )
var ErrBadSettingRange = errors . New ( "Only integers between a certain range are allowed in this setting" )
// To avoid leaking internal state to the user
// TODO: We need to add some sort of DualError interface
func SafeSettingError ( err error ) bool {
return err == ErrNotInteger || err == ErrSettingNotInteger || err == ErrBadConstraintNotInteger || err == ErrBadSettingRange || err == ErrNoRows
}
2017-09-10 16:57:22 +00:00
// TODO: Add better support for HTML attributes (html-attribute). E.g. Meta descriptions.
2017-11-23 05:37:08 +00:00
func ( sBox SettingMap ) ParseSetting ( sname string , scontent string , stype string , constraint string ) ( err error ) {
2017-09-10 16:57:22 +00:00
var ssBox = map [ string ] interface { } ( sBox )
2017-11-23 05:37:08 +00:00
switch stype {
case "bool" :
2017-08-20 09:39:02 +00:00
ssBox [ sname ] = ( scontent == "1" )
2017-11-23 05:37:08 +00:00
case "int" :
2017-08-20 09:39:02 +00:00
ssBox [ sname ] , err = strconv . Atoi ( scontent )
2016-12-09 13:46:29 +00:00
if err != nil {
2017-11-23 05:37:08 +00:00
return ErrNotInteger
2016-12-09 13:46:29 +00:00
}
2017-11-23 05:37:08 +00:00
case "int64" :
2017-08-20 09:39:02 +00:00
ssBox [ sname ] , err = strconv . ParseInt ( scontent , 10 , 64 )
2016-12-09 13:46:29 +00:00
if err != nil {
2017-11-23 05:37:08 +00:00
return ErrNotInteger
2016-12-09 13:46:29 +00:00
}
2017-11-23 05:37:08 +00:00
case "list" :
2017-09-03 04:50:31 +00:00
cons := strings . Split ( constraint , "-" )
2016-12-21 02:30:32 +00:00
if len ( cons ) < 2 {
2017-11-23 05:37:08 +00:00
return errors . New ( "Invalid constraint! The second field wasn't set!" )
2016-12-21 02:30:32 +00:00
}
2017-06-10 07:58:15 +00:00
2016-12-21 02:30:32 +00:00
con1 , err := strconv . Atoi ( cons [ 0 ] )
2017-11-08 07:28:33 +00:00
con2 , err2 := strconv . Atoi ( cons [ 1 ] )
if err != nil || err2 != nil {
2017-11-23 05:37:08 +00:00
return ErrBadConstraintNotInteger
2016-12-21 02:30:32 +00:00
}
2017-06-10 07:58:15 +00:00
2017-09-03 04:50:31 +00:00
value , err := strconv . Atoi ( scontent )
2016-12-21 02:30:32 +00:00
if err != nil {
2017-11-23 05:37:08 +00:00
return ErrSettingNotInteger
2016-12-21 02:30:32 +00:00
}
2017-06-10 07:58:15 +00:00
2016-12-21 02:30:32 +00:00
if value < con1 || value > con2 {
2017-11-23 05:37:08 +00:00
return ErrBadSettingRange
2016-12-21 02:30:32 +00:00
}
2017-08-20 09:39:02 +00:00
ssBox [ sname ] = value
2017-11-23 05:37:08 +00:00
default :
2017-08-20 09:39:02 +00:00
ssBox [ sname ] = scontent
2016-12-09 13:46:29 +00:00
}
2017-11-23 05:37:08 +00:00
return nil
2017-06-10 07:58:15 +00:00
}
2017-11-13 09:23:43 +00:00
func ( sBox SettingMap ) BypassGet ( name string ) ( * Setting , error ) {
setting := & Setting { Name : name }
err := settingStmts . get . QueryRow ( name ) . Scan ( & setting . Content , & setting . Type , & setting . Constraint )
return setting , err
}
2017-11-23 05:37:08 +00:00
func ( sBox SettingMap ) BypassGetAll ( ) ( settingList [ ] * Setting , err error ) {
rows , err := settingStmts . getAll . Query ( )
if err != nil {
return nil , err
}
defer rows . Close ( )
for rows . Next ( ) {
setting := & Setting { Name : "" }
err := rows . Scan ( & setting . Name , & setting . Content , & setting . Type , & setting . Constraint )
if err != nil {
return nil , err
}
settingList = append ( settingList , setting )
}
return settingList , rows . Err ( )
}
func ( sBox SettingMap ) Update ( name string , content string ) error {
setting , err := sBox . BypassGet ( name )
if err == ErrNoRows {
return err
}
// TODO: Why is this here and not in a common function?
if setting . Type == "bool" {
if content == "on" || content == "1" {
content = "1"
} else {
content = "0"
}
}
// TODO: Make this a method or function?
_ , err = settingStmts . update . Exec ( content , name )
if err != nil {
return err
}
err = sBox . ParseSetting ( name , content , setting . Type , setting . Constraint )
if err != nil {
return err
}
// TODO: Do a reload instead?
SettingBox . Store ( sBox )
return nil
}