-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconfig.go
190 lines (157 loc) · 4.66 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
package main
import (
"bytes"
"fmt"
"io"
"log"
"github.com/cdfmlr/ellipsis"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"golang.org/x/exp/slog"
"gopkg.in/yaml.v3"
)
// Config is the configuration for the application.
type Config struct {
// SrvAddr is the address:port to listen on.
SrvAddr string
// EnabledSayer is the name of the sayer to use: "azure".
EnabledSayer string
// AzureSayer is the configuration for the AzureSayer.
AzureSayer AzureSayerConfig
}
func (c *Config) Write(dst io.Writer) error {
return yaml.NewEncoder(dst).Encode(&c)
}
// DesensitizedCopy desensitize the config.
// Returns a pointer to the desensitized config copy.
//
// If it's failed to make it, it panics.
//
// Avoid keys being printed to the log.
func (c *Config) DesensitizedCopy() *Config {
var cCopy Config
// deep copy
buf := bytes.NewBuffer(nil)
if err := yaml.NewEncoder(buf).Encode(&c); err != nil {
panic(err)
}
if err := yaml.NewDecoder(buf).Decode(&cCopy); err != nil {
panic(err)
}
// api key
cCopy.AzureSayer.SpeechKey = ellipsis.Centering(cCopy.AzureSayer.SpeechKey, 9)
return &cCopy
}
// AzureSayerConfig is the configuration for the AzureSayer.
type AzureSayerConfig struct {
// SpeechKey is the key for the Azure Speech API.
SpeechKey string
// SpeechRegion is the region for the Azure Speech API.
SpeechRegion string
// Roles is a map from role to voiceTemplate.
Roles map[string]string
// FormatMicrosoft is the format for the Azure Speech API.
FormatMicrosoft string
// FormatMimeSubtype is the mime subtype for the audio format.
FormatMimeSubtype string
}
var config Config
var configChanged chan struct{}
func initConfig(paths ...string) {
defaultConfig()
setupConfig(paths...)
if err := readConfig(); err != nil {
log.Fatal(err)
}
if err := reloadConfig(); err != nil {
log.Fatal("loading config failed: ", err)
}
log.Println("Config loaded:")
config.DesensitizedCopy().Write(log.Writer())
configChanged = watchConfig()
}
func defaultConfig() {
viper.SetDefault("srv_addr", "50010")
viper.SetDefault("enabled_sayer", "azure")
viper.SetDefault("azure_sayer.speech_key", "")
viper.SetDefault("azure_sayer.speech_region", "")
viper.SetDefault("azure_sayer.roles", map[string]string{})
viper.SetDefault("azure_sayer.format_microsoft", "audio-16khz-32kbitrate-mono-mp3")
viper.SetDefault("azure_sayer.format_mime_subtype", "mp3")
}
func setupConfig(paths ...string) {
// XXX: Env vars does not work: https://github.com/spf13/viper/issues/188
// I tried all the workarounds, but none worked.
// viper.SetEnvPrefix("mes")
// viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
// viper.AutomaticEnv()
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.AddConfigPath("/etc/extremesayer")
viper.AddConfigPath("$HOME/.extremesayer")
for _, path := range paths {
if path == "" {
continue
}
viper.SetConfigFile(path)
}
}
func readConfig() error {
if err := viper.ReadInConfig(); err != nil {
switch err.(type) {
case viper.ConfigFileNotFoundError:
return fmt.Errorf("no config file found, using defaults")
default:
return fmt.Errorf("reading config file failed. err: %v", err)
}
}
return nil
}
// watchConfig watches the config file for changes and reloads it.
// It returns a channel that will be sent to when the config is reloaded.
func watchConfig() chan struct{} {
ch := make(chan struct{}, 10)
viper.OnConfigChange(func(e fsnotify.Event) {
slog.Warn("Config file changed.", "file", e.Name)
if err := reloadConfig(); err != nil {
slog.Error("reloading config", "err", err)
}
slog.Info("reloaded config successfully.")
ch <- struct{}{}
})
viper.WatchConfig()
return ch
}
func checkConfig() error {
if config.SrvAddr == "" {
return fmt.Errorf("SrvAddr must be set")
}
if config.EnabledSayer == "azure" {
if config.AzureSayer.SpeechKey == "" {
return fmt.Errorf("azure_sayer.speech_key must be set")
}
if config.AzureSayer.SpeechRegion == "" {
return fmt.Errorf("azure_sayer.speech_region must be set")
}
if len(config.AzureSayer.Roles) == 0 {
return fmt.Errorf("azure_sayer.roles must be set")
}
}
if config.EnabledSayer != "azure" {
return fmt.Errorf("enabled_sayer must be 'azure'")
}
return nil
}
func reloadConfig() error {
if err := viper.Unmarshal(&config); err != nil {
return fmt.Errorf("reloaded config: Unmarshal failed. err=%v", err)
}
// j, _ := json.MarshalIndent(config, "", " ")
// fmt.Printf("reloaded config: %s\n", string(j))
// fmt.Printf("roles: %v\n", config.AzureSayer.Roles)
if err := checkConfig(); err != nil {
return fmt.Errorf("reloaded config: checkConfig failed. err=%v", err)
}
return nil
}