package common import ( "database/sql" "errors" "strconv" "strings" "sync/atomic" "github.com/Azareal/Gosora/query_gen" ) var SettingBox atomic.Value // An atomic value pointing to a SettingBox // 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{} type SettingStore interface { ParseSetting(sname string, scontent string, stype string, sconstraint string) string BypassGet(name string) (*Setting, error) BypassGetAll(name string) ([]*Setting, error) } type OptionLabel struct { Label string Value int Selected bool } type Setting struct { Name string Content string Type string Constraint string } type SettingStmts struct { getAll *sql.Stmt get *sql.Stmt update *sql.Stmt } var settingStmts SettingStmts func init() { SettingBox.Store(SettingMap(make(map[string]interface{}))) DbInits.Add(func(acc *qgen.Accumulator) error { settingStmts = SettingStmts{ getAll: acc.Select("settings").Columns("name, content, type, constraints").Prepare(), get: acc.Select("settings").Columns("content, type, constraints").Where("name = ?").Prepare(), update: acc.Update("settings").Set("content = ?").Where("name = ?").Prepare(), } return acc.FirstError() }) } func (setting *Setting) Copy() (out *Setting) { out = &Setting{Name: ""} *out = *setting return out } func LoadSettings() error { var sBox = SettingMap(make(map[string]interface{})) settings, err := sBox.BypassGetAll() if err != nil { return err } for _, setting := range settings { err = sBox.ParseSetting(setting.Name, setting.Content, setting.Type, setting.Constraint) if err != nil { return err } } SettingBox.Store(sBox) return nil } // 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 } // TODO: Add better support for HTML attributes (html-attribute). E.g. Meta descriptions. func (sBox SettingMap) ParseSetting(sname string, scontent string, stype string, constraint string) (err error) { var ssBox = map[string]interface{}(sBox) switch stype { case "bool": ssBox[sname] = (scontent == "1") case "int": ssBox[sname], err = strconv.Atoi(scontent) if err != nil { return ErrNotInteger } case "int64": ssBox[sname], err = strconv.ParseInt(scontent, 10, 64) if err != nil { return ErrNotInteger } case "list": cons := strings.Split(constraint, "-") if len(cons) < 2 { return errors.New("Invalid constraint! The second field wasn't set!") } con1, err := strconv.Atoi(cons[0]) con2, err2 := strconv.Atoi(cons[1]) if err != nil || err2 != nil { return ErrBadConstraintNotInteger } value, err := strconv.Atoi(scontent) if err != nil { return ErrSettingNotInteger } if value < con1 || value > con2 { return ErrBadSettingRange } ssBox[sname] = value default: ssBox[sname] = scontent } return nil } 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 } 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 }