github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/common/settings.go (about) 1 package common 2 3 import ( 4 "database/sql" 5 "errors" 6 "strconv" 7 "strings" 8 "sync/atomic" 9 10 qgen "github.com/Azareal/Gosora/query_gen" 11 ) 12 13 var SettingBox atomic.Value // An atomic value pointing to a SettingBox 14 15 // 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 16 type SettingMap map[string]interface{} 17 18 type SettingStore interface { 19 ParseSetting(name, content, typ, constraint string) string 20 BypassGet(name string) (*Setting, error) 21 BypassGetAll(name string) ([]*Setting, error) 22 } 23 24 type OptionLabel struct { 25 Label string 26 Value int 27 Selected bool 28 } 29 30 type Setting struct { 31 Name string 32 Content string 33 Type string 34 Constraint string 35 } 36 37 type SettingStmts struct { 38 getAll *sql.Stmt 39 get *sql.Stmt 40 update *sql.Stmt 41 } 42 43 var settingStmts SettingStmts 44 45 func init() { 46 SettingBox.Store(SettingMap(make(map[string]interface{}))) 47 DbInits.Add(func(acc *qgen.Accumulator) error { 48 s := "settings" 49 settingStmts = SettingStmts{ 50 getAll: acc.Select(s).Columns("name,content,type,constraints").Prepare(), 51 get: acc.Select(s).Columns("content,type,constraints").Where("name=?").Prepare(), 52 update: acc.Update(s).Set("content=?").Where("name=?").Prepare(), 53 } 54 return acc.FirstError() 55 }) 56 } 57 58 func (s *Setting) Copy() (o *Setting) { 59 o = &Setting{Name: ""} 60 *o = *s 61 return o 62 } 63 64 func LoadSettings() error { 65 sBox := SettingMap(make(map[string]interface{})) 66 settings, err := sBox.BypassGetAll() 67 if err != nil { 68 return err 69 } 70 71 for _, s := range settings { 72 err = sBox.ParseSetting(s.Name, s.Content, s.Type, s.Constraint) 73 if err != nil { 74 return err 75 } 76 } 77 78 SettingBox.Store(sBox) 79 return nil 80 } 81 82 // TODO: Add better support for HTML attributes (html-attribute). E.g. Meta descriptions. 83 func (sBox SettingMap) ParseSetting(name, content, typ, constraint string) (err error) { 84 ssBox := map[string]interface{}(sBox) 85 switch typ { 86 case "bool": 87 ssBox[name] = (content == "1") 88 case "int": 89 ssBox[name], err = strconv.Atoi(content) 90 if err != nil { 91 return errors.New("You were supposed to enter an integer x.x") 92 } 93 case "int64": 94 ssBox[name], err = strconv.ParseInt(content, 10, 64) 95 if err != nil { 96 return errors.New("You were supposed to enter an integer x.x") 97 } 98 case "list": 99 cons := strings.Split(constraint, "-") 100 if len(cons) < 2 { 101 return errors.New("Invalid constraint! The second field wasn't set!") 102 } 103 104 con1, err := strconv.Atoi(cons[0]) 105 con2, err2 := strconv.Atoi(cons[1]) 106 if err != nil || err2 != nil { 107 return errors.New("Invalid contraint! The constraint field wasn't an integer!") 108 } 109 110 val, err := strconv.Atoi(content) 111 if err != nil { 112 return errors.New("Only integers are allowed in this setting x.x") 113 } 114 115 if val < con1 || val > con2 { 116 return errors.New("Only integers between a certain range are allowed in this setting") 117 } 118 ssBox[name] = val 119 default: 120 ssBox[name] = content 121 } 122 return nil 123 } 124 125 func (sBox SettingMap) BypassGet(name string) (*Setting, error) { 126 s := &Setting{Name: name} 127 err := settingStmts.get.QueryRow(name).Scan(&s.Content, &s.Type, &s.Constraint) 128 return s, err 129 } 130 131 func (sBox SettingMap) BypassGetAll() (settingList []*Setting, err error) { 132 rows, err := settingStmts.getAll.Query() 133 if err != nil { 134 return nil, err 135 } 136 defer rows.Close() 137 138 for rows.Next() { 139 s := &Setting{Name: ""} 140 err := rows.Scan(&s.Name, &s.Content, &s.Type, &s.Constraint) 141 if err != nil { 142 return nil, err 143 } 144 settingList = append(settingList, s) 145 } 146 return settingList, rows.Err() 147 } 148 149 func (sBox SettingMap) Update(name, content string) RouteError { 150 s, err := sBox.BypassGet(name) 151 if err == ErrNoRows { 152 return FromError(err) 153 } else if err != nil { 154 return SysError(err.Error()) 155 } 156 157 // TODO: Why is this here and not in a common function? 158 if s.Type == "bool" { 159 if content == "on" || content == "1" { 160 content = "1" 161 } else { 162 content = "0" 163 } 164 } 165 166 err = sBox.ParseSetting(name, content, s.Type, s.Constraint) 167 if err != nil { 168 return FromError(err) 169 } 170 171 // TODO: Make this a method or function? 172 _, err = settingStmts.update.Exec(content, name) 173 if err != nil { 174 return SysError(err.Error()) 175 } 176 177 err = LoadSettings() 178 if err != nil { 179 return SysError(err.Error()) 180 } 181 return nil 182 }