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  }