211 lines
6.1 KiB
Go
211 lines
6.1 KiB
Go
package config
|
|
|
|
import (
|
|
"aleesa-webapp-go/internal/defaults"
|
|
"aleesa-webapp-go/internal/log"
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"unsafe"
|
|
|
|
"github.com/cockroachdb/pebble"
|
|
"github.com/go-redis/redis/v8"
|
|
"github.com/hjson/hjson-go"
|
|
)
|
|
|
|
// MyConfig структурка, описывающая конфиг.
|
|
type MyConfig struct {
|
|
Server string `json:"server,omitempty"`
|
|
Port int `json:"port,omitempty"`
|
|
Timeout int `json:"timeout,omitempty"`
|
|
Loglevel string `json:"loglevel,omitempty"`
|
|
Log string `json:"log,omitempty"`
|
|
Channel string `json:"channel,omitempty"`
|
|
DataDir string `json:"datadir,omitempty"`
|
|
Csign string `json:"csign,omitempty"`
|
|
ForwardsMax int64 `json:"forwards_max,omitempty"`
|
|
OpenWeatherMap struct {
|
|
Enabled bool `json:"enabled,omitempty"`
|
|
Country bool `json:"country,omitempty"`
|
|
Appid string `json:"appid,omitempty"`
|
|
} `json:"openweathermap,omitempty"`
|
|
Flickr struct {
|
|
Enabled bool `json:"enabled,omitempty"`
|
|
Key string `json:"key,omitempty"`
|
|
Secret string `json:"secret,omitempty"`
|
|
OAuthToken string `json:"oauth_token,omitempty"`
|
|
OAuthTokenSecret string `json:"oauth_token_secret,omitempty"`
|
|
} `json:"flickr,omitempty"`
|
|
UserAgents []string `json:"user_agents,omitempty"`
|
|
PcacheDB map[string]*pebble.DB
|
|
RedisClient *redis.Client
|
|
}
|
|
|
|
// ReadConfig читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге
|
|
// нет.
|
|
func ReadConfig() (*MyConfig, error) {
|
|
locations, err := defaults.DefaultConfigFileLocations()
|
|
|
|
if err != nil {
|
|
return &MyConfig{}, err
|
|
}
|
|
|
|
for _, location := range locations {
|
|
fileInfo, err := os.Stat(location)
|
|
|
|
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата
|
|
if fileInfo.Size() > defaults.ConfigFileSize {
|
|
log.Warnf("Config file %s is too long for config, skipping", location)
|
|
|
|
continue
|
|
}
|
|
|
|
buf, err := os.ReadFile(location)
|
|
|
|
// Не удалось прочитать, попробуем следующего кандидата
|
|
if err != nil {
|
|
log.Warnf("Skip reading config file %s: %s", location, err)
|
|
|
|
continue
|
|
}
|
|
|
|
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
|
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
|
// сериализуем в json, а потом json преврщааем в стркутурку. Не очень эффективно, но он и не часто требуется.
|
|
var (
|
|
sampleConfig *MyConfig
|
|
tmp map[string]interface{}
|
|
)
|
|
|
|
err = hjson.Unmarshal(buf, &tmp)
|
|
|
|
// Не удалось распарсить - попробуем следующего кандидата
|
|
if err != nil {
|
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
|
|
|
continue
|
|
}
|
|
|
|
tmpjson, err := json.Marshal(tmp)
|
|
|
|
// Не удалось преобразовать map-ку в json
|
|
if err != nil {
|
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
|
|
|
continue
|
|
}
|
|
|
|
if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil {
|
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
|
|
|
continue
|
|
}
|
|
|
|
// Валидируем значения из конфига
|
|
if sampleConfig.Server == "" {
|
|
sampleConfig.Server = defaults.Host
|
|
}
|
|
|
|
if sampleConfig.Port == 0 {
|
|
sampleConfig.Port = defaults.RedisPort
|
|
}
|
|
|
|
if sampleConfig.Timeout == 0 {
|
|
sampleConfig.Timeout = defaults.NetworkTimeout
|
|
}
|
|
|
|
if sampleConfig.Loglevel == "" {
|
|
sampleConfig.Loglevel = defaults.Loglevel
|
|
}
|
|
|
|
// sampleConfig.Log = "" if not set
|
|
|
|
if sampleConfig.Channel == "" {
|
|
log.Errorf("Channel field in config file %s must be set", location)
|
|
|
|
continue
|
|
}
|
|
|
|
if sampleConfig.DataDir == "" {
|
|
sampleConfig.DataDir = defaults.DataDir
|
|
}
|
|
|
|
if sampleConfig.Csign == "" {
|
|
log.Errorf("Csign field in config file %s must be set", location)
|
|
|
|
continue
|
|
}
|
|
|
|
if sampleConfig.ForwardsMax == 0 {
|
|
sampleConfig.ForwardsMax = defaults.ForwardMax
|
|
}
|
|
|
|
if unsafe.Sizeof(sampleConfig.OpenWeatherMap) == 0 {
|
|
log.Errorf("Hash OpenWeatherMap in config file %s must be defined", location)
|
|
|
|
continue
|
|
}
|
|
|
|
if sampleConfig.OpenWeatherMap.Appid == "" {
|
|
log.Errorf("Openweathermap->appid in config file %s must be set to its valid value", location)
|
|
|
|
continue
|
|
}
|
|
|
|
// sampleConfig.OpenWeatherMap.Country is false by default
|
|
|
|
// Теперь надо бы попробовать загрузить список User Agent-ов, с которыми ходить в разные апишки.
|
|
uaFile := fmt.Sprintf("%s/useragents.txt", sampleConfig.DataDir)
|
|
uaList, err := ReadLines(uaFile)
|
|
|
|
if err != nil {
|
|
return &MyConfig{}, fmt.Errorf("unable to read %s: %w", uaFile, err)
|
|
}
|
|
|
|
sampleConfig.UserAgents = uaList
|
|
|
|
sampleConfig.PcacheDB = make(map[string]*pebble.DB)
|
|
|
|
log.Infof("Using %s as config file", location)
|
|
|
|
return sampleConfig, nil
|
|
}
|
|
|
|
return &MyConfig{}, fmt.Errorf("config was not loaded! Refusing to start")
|
|
}
|
|
|
|
// ReadLines читает даденный файл построчно в массив строк.
|
|
func ReadLines(path string) ([]string, error) {
|
|
file, err := os.Open(path)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer func(file *os.File) {
|
|
err := file.Close()
|
|
if err != nil {
|
|
log.Errorf("Unable to close file %s:%s", path, err)
|
|
}
|
|
}(file)
|
|
|
|
var (
|
|
lines []string
|
|
scanner = bufio.NewScanner(file)
|
|
)
|
|
|
|
for scanner.Scan() {
|
|
lines = append(lines, scanner.Text())
|
|
}
|
|
|
|
return lines, scanner.Err()
|
|
}
|
|
|
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|